@k2b/ui 0.38.0 → 0.39.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.
Files changed (31) hide show
  1. package/dist/browser/actions/Dropdown.js +42 -6
  2. package/dist/browser/actions/Dropdown.js.map +1 -1
  3. package/dist/browser/actions/SelectionToolbar.js +11 -6
  4. package/dist/browser/actions/SelectionToolbar.js.map +1 -1
  5. package/dist/browser/actions/Toolbar.js +5 -1
  6. package/dist/browser/actions/Toolbar.js.map +1 -1
  7. package/dist/browser/actions/index.js.map +1 -1
  8. package/dist/browser/chat/ChatComposer.js +11 -2
  9. package/dist/browser/chat/ChatComposer.js.map +1 -1
  10. package/dist/browser/text-editor/TextEditor.js +331 -214
  11. package/dist/browser/text-editor/TextEditor.js.map +1 -1
  12. package/dist/browser/text-editor/table.js +43 -6
  13. package/dist/browser/text-editor/table.js.map +1 -1
  14. package/dist/ssr/actions/Dropdown.js +38 -6
  15. package/dist/ssr/actions/Dropdown.js.map +1 -1
  16. package/dist/ssr/actions/SelectionToolbar.js +11 -6
  17. package/dist/ssr/actions/SelectionToolbar.js.map +1 -1
  18. package/dist/ssr/actions/Toolbar.js +5 -1
  19. package/dist/ssr/actions/Toolbar.js.map +1 -1
  20. package/dist/ssr/actions/index.js.map +1 -1
  21. package/dist/ssr/chat/ChatComposer.js +10 -2
  22. package/dist/ssr/chat/ChatComposer.js.map +1 -1
  23. package/dist/ssr/text-editor/TextEditor.js +225 -96
  24. package/dist/ssr/text-editor/TextEditor.js.map +1 -1
  25. package/dist/ssr/text-editor/table.js +43 -6
  26. package/dist/ssr/text-editor/table.js.map +1 -1
  27. package/dist/styles.css +1 -1
  28. package/dist/types/actions/Dropdown.d.ts +15 -1
  29. package/dist/types/actions/Toolbar.d.ts +6 -1
  30. package/dist/types/actions/index.d.ts +1 -1
  31. package/package.json +1 -1
@@ -20,17 +20,17 @@ var _tmpl$ = /*#__PURE__*/_$template(`<div class=k2b-ste-size-picker><!$><!/><!$
20
20
  _tmpl$4 = /*#__PURE__*/_$template(`<div class=k2b-ste-size-row>`),
21
21
  _tmpl$5 = /*#__PURE__*/_$template(`<button type=button tabindex=-1>`),
22
22
  _tmpl$6 = /*#__PURE__*/_$template(`<label class=k2b-ste-recovery-label><!$><!/><textarea class=k2b-ste-recovery readonly>`),
23
- _tmpl$7 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
23
+ _tmpl$7 = /*#__PURE__*/_$template(`<div class=k2b-ste-table-sheet-heading><span></span><!$><!/>`),
24
24
  _tmpl$8 = /*#__PURE__*/_$template(`<i class="ti ti-chevron-down"aria-hidden=true>`),
25
- _tmpl$9 = /*#__PURE__*/_$template(`<div class=k2b-ste-table-sheet-heading><span></span><!$><!/>`),
26
- _tmpl$0 = /*#__PURE__*/_$template(`<span class=k2b-ste-check><i class="ti ti-alert-triangle"aria-hidden=true></i><!$><!/>`),
27
- _tmpl$1 = /*#__PURE__*/_$template(`<div class=k2b-ste-status role=status><span></span><!$><!/>`),
28
- _tmpl$10 = /*#__PURE__*/_$template(`<div class=k2b-ste-toolbar-slot>`),
29
- _tmpl$11 = /*#__PURE__*/_$template(`<div class=k2b-ste-loading role=status>`),
30
- _tmpl$12 = /*#__PURE__*/_$template(`<div class=k2b-ste-floating-menu>`),
25
+ _tmpl$9 = /*#__PURE__*/_$template(`<i class="ti ti-copy"aria-hidden=true>`),
26
+ _tmpl$0 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
27
+ _tmpl$1 = /*#__PURE__*/_$template(`<span class=k2b-ste-check><i class="ti ti-alert-triangle"aria-hidden=true></i><!$><!/>`),
28
+ _tmpl$10 = /*#__PURE__*/_$template(`<div class=k2b-ste-status role=status><span></span><!$><!/>`),
29
+ _tmpl$11 = /*#__PURE__*/_$template(`<div class=k2b-ste-toolbar-slot>`),
30
+ _tmpl$12 = /*#__PURE__*/_$template(`<div class=k2b-ste-loading role=status>`),
31
31
  _tmpl$13 = /*#__PURE__*/_$template(`<i class="ti ti-x"aria-hidden=true>`),
32
32
  _tmpl$14 = /*#__PURE__*/_$template(`<div class=k2b-ste-notice><div class=k2b-ste-notice-content><!$><!/><!$><!/></div><!$><!/>`),
33
- _tmpl$15 = /*#__PURE__*/_$template(`<div><span class=k2b-ste-announcement role=status aria-live=polite aria-atomic=true></span><!$><!/><!$><!/><!$><!/><div></div><!$><!/><div role=status aria-live=polite aria-atomic=true>`),
33
+ _tmpl$15 = /*#__PURE__*/_$template(`<div><span class=k2b-ste-announcement role=status aria-live=polite aria-atomic=true></span><!$><!/><!$><!/><!$><!/><div></div><div class=k2b-ste-floating-menu></div><div role=status aria-live=polite aria-atomic=true>`),
34
34
  _tmpl$16 = /*#__PURE__*/_$template(`<div><span>`),
35
35
  _tmpl$17 = /*#__PURE__*/_$template(`<span>`);
36
36
  import { history, historyKeymap, redo, undo } from "@codemirror/commands";
@@ -344,10 +344,27 @@ export function TextEditor(props) {
344
344
  const [documentRevision, setDocumentRevision] = createSignal(0);
345
345
  const [phoneMenu, setPhoneMenu] = createSignal(false);
346
346
  const [platform, setPlatform] = createSignal("other");
347
+ /** What the open cell menu acts on: a row grip, a column grip, or the cell (right-click, keys, toolbar). */
348
+ const [scope, setScope] = createSignal("cell");
349
+ let grip;
347
350
  createEffect(() => {
348
351
  const target = cell();
349
- target?.setAttribute("data-active", "");
350
- onCleanup(() => target?.removeAttribute("data-active"));
352
+ const kind = scope();
353
+ if (!target) return;
354
+ if (kind === "cell") {
355
+ target.setAttribute("data-active", "");
356
+ onCleanup(() => target.removeAttribute("data-active"));
357
+ return;
358
+ }
359
+ // A grip's menu marks its whole row or column, and the grip stays visible while the menu is open.
360
+ const marked = kind === "row" ? [target.closest("tr")] : [...(target.closest(".k2b-ste-table")?.querySelectorAll(`.k2b-ste-cell[data-c="${target.dataset.c}"]`) ?? [])].map(element => element.parentElement);
361
+ const opener = grip;
362
+ for (const element of marked) element?.setAttribute("data-marked", "");
363
+ opener?.setAttribute("data-open", "");
364
+ onCleanup(() => {
365
+ for (const element of marked) element?.removeAttribute("data-marked");
366
+ opener?.removeAttribute("data-open");
367
+ });
351
368
  });
352
369
  let disposed = false;
353
370
  onCleanup(() => {
@@ -415,8 +432,9 @@ export function TextEditor(props) {
415
432
  show();
416
433
  };
417
434
  let menuOpen = false;
418
- let pointerAnchor;
419
- let floatingTrigger;
435
+ let openCellMenu;
436
+ let closeCellMenu;
437
+ let copyTrigger;
420
438
  const tableTriggers = new Set();
421
439
  let frame = 0;
422
440
  const [menuPosition, setMenuPosition] = createSignal({
@@ -444,22 +462,19 @@ export function TextEditor(props) {
444
462
  const wrap = target.closest(".k2b-ste-table");
445
463
  const table = wrap?.querySelector("table");
446
464
  if (!target.isConnected || !wrap || !table || mode() !== "formatted") {
465
+ closeCellMenu?.();
447
466
  menuOpen = false;
448
467
  setCell(undefined);
449
468
  checkFocus();
450
469
  return;
451
470
  }
452
- const rect = target.getBoundingClientRect();
471
+ // Only the read-only phone copy control sits below its table; every other menu opens beside its anchor.
472
+ if (!props.readOnly) return;
453
473
  const bounds = root.getBoundingClientRect();
454
- const right = Math.min(table.getBoundingClientRect().right, wrap.getBoundingClientRect().right);
455
- const position = props.readOnly ? {
474
+ setMenuPosition({
456
475
  left: `${wrap.getBoundingClientRect().left - bounds.left}px`,
457
476
  top: `${wrap.getBoundingClientRect().bottom - bounds.top - (window.matchMedia("(max-width: 47.999rem)").matches ? 44 : 0)}px`
458
- } : {
459
- left: `${right - bounds.left - 24}px`,
460
- top: `${rect.top - bounds.top + (rect.height - 24) / 2}px`
461
- };
462
- setMenuPosition(pointerAnchor ?? position);
477
+ });
463
478
  };
464
479
  const schedulePosition = () => {
465
480
  if (!frame) frame = requestAnimationFrame(() => {
@@ -521,10 +536,12 @@ export function TextEditor(props) {
521
536
  const header = cell()?.closest(".k2b-ste-table")?.querySelector(`.k2b-ste-cell[data-r="0"][data-c="${column}"]`);
522
537
  return t().menuColumn(header?.dataset.raw || t().column(column + 1));
523
538
  };
524
- const menuHeading = () => `${menuRow()} · ${menuColumn()}`;
525
- const actionItem = (action, choice) => ({
539
+ const menuHeading = (kind = scope()) => kind === "row" ? menuRow() : kind === "column" ? menuColumn() : `${menuRow()} · ${menuColumn()}`;
540
+ const actionItem = (action, choice, kind = "cell") => ({
526
541
  get label() {
527
- return phoneMenu() && action in shortActionLabels ? t()[shortActionLabels[action]] : t()[action];
542
+ // A grip's sheet has no row or column heading over its entries, so deleting keeps its full name.
543
+ const short = phoneMenu() && action in shortActionLabels && !(kind !== "cell" && action.startsWith("delete"));
544
+ return short ? t()[shortActionLabels[action]] : t()[action];
528
545
  },
529
546
  icon: `ti ti-${actionIcons[action]}`,
530
547
  get disabled() {
@@ -594,25 +611,22 @@ export function TextEditor(props) {
594
611
  void copy("markdown");
595
612
  }
596
613
  }];
597
- const items = () => {
614
+ /** The row grip shows row and table actions, the column grip column, alignment and table actions; the rest everything. */
615
+ const items = kind => {
598
616
  if (props.readOnly) return copyItems();
599
617
  const align = () => {
600
618
  documentRevision();
601
619
  return cell()?.parentElement?.style.textAlign || "left";
602
620
  };
603
- return [{
604
- get sectionLabel() {
605
- return menuHeading();
606
- },
607
- items: []
608
- }, {
621
+ const row = [{
609
622
  get sectionLabel() {
610
- return menuRow();
623
+ return kind === "cell" ? menuRow() : undefined;
611
624
  },
612
- items: ["rowAbove", "rowBelow", "moveRowUp", "moveRowDown", "deleteRow"].map(action => actionItem(action))
613
- }, {
625
+ items: ["rowAbove", "rowBelow", "moveRowUp", "moveRowDown", "deleteRow"].map(action => actionItem(action, undefined, kind))
626
+ }];
627
+ const column = [{
614
628
  get sectionLabel() {
615
- return menuColumn();
629
+ return kind === "cell" ? menuColumn() : undefined;
616
630
  },
617
631
  items: ["colLeft", "colRight", "moveColLeft", "moveColRight"].map(action => actionItem(action))
618
632
  }, {
@@ -625,8 +639,14 @@ export function TextEditor(props) {
625
639
  checked: () => align() === ["left", "center", "right"][index]
626
640
  }))
627
641
  }, {
628
- items: [actionItem("deleteCol")]
629
- }, {
642
+ items: [actionItem("deleteCol", undefined, kind)]
643
+ }];
644
+ return [{
645
+ get sectionLabel() {
646
+ return menuHeading(kind);
647
+ },
648
+ items: []
649
+ }, ...(kind === "column" ? [] : row), ...(kind === "row" ? [] : column), {
630
650
  sectionLabel: t().table,
631
651
  items: [actionItem("toggleHeader", {
632
652
  choice: "checkbox",
@@ -652,89 +672,132 @@ export function TextEditor(props) {
652
672
  const menuChange = open => {
653
673
  menuOpen = open;
654
674
  };
675
+ // A press beside an open table menu only closes it: no cell takes focus, no
676
+ // text is selected and no column is marked. The menu's light dismiss closes it.
677
+ let tableMenuOpen = false;
678
+ const tableMenuChange = open => {
679
+ menuOpen = open;
680
+ tableMenuOpen = open && !phoneMenu();
681
+ };
682
+ let swallow;
683
+ const pressBesideMenu = event => {
684
+ swallow?.abort();
685
+ if (!tableMenuOpen || event.target instanceof Element && event.target.closest(".k2b-ste-table-menu")) return;
686
+ event.stopPropagation();
687
+ const current = new AbortController();
688
+ swallow = current;
689
+ const block = blocked => {
690
+ blocked.preventDefault();
691
+ blocked.stopPropagation();
692
+ if (blocked.type === "click" || blocked.type === "contextmenu") current.abort();
693
+ };
694
+ for (const type of ["mousedown", "mouseup", "click", "contextmenu"]) document.addEventListener(type, block, {
695
+ capture: true,
696
+ signal: current.signal
697
+ });
698
+ // Only this press is swallowed: its click follows the release in the same task, and a cancelled press never
699
+ // clicks. A key other than a modifier, which may come with the press, starts new input.
700
+ const end = () => current.abort();
701
+ document.addEventListener("pointerup", () => setTimeout(end), {
702
+ capture: true,
703
+ once: true,
704
+ signal: current.signal
705
+ });
706
+ document.addEventListener("pointercancel", end, {
707
+ capture: true,
708
+ once: true,
709
+ signal: current.signal
710
+ });
711
+ document.addEventListener("keydown", key => {
712
+ if (!["Shift", "Control", "Alt", "Meta"].includes(key.key)) end();
713
+ }, {
714
+ capture: true,
715
+ signal: current.signal
716
+ });
717
+ };
655
718
  const menuClose = () => {
656
719
  menuOpen = false;
657
- pointerAnchor = undefined;
720
+ tableMenuOpen = false;
721
+ setScope("cell");
722
+ grip = undefined;
658
723
  schedulePosition();
659
724
  queueMicrotask(() => {
660
725
  if (disposed) return;
661
726
  const focused = document.activeElement;
662
- if (focused instanceof HTMLElement && focused.matches(".k2b-ste-cell-menu, .k2b-ste-table-tool")) cell()?.focus();
727
+ if (focused instanceof HTMLElement && focused.matches(".k2b-ste-copy-menu, .k2b-ste-table-tool")) cell()?.focus();
663
728
  checkFocus();
664
729
  });
665
730
  };
666
- const menu = (floating, trigger) => // A sheet owns its item inventory until it closes. Replace that owner when
731
+ const sheetTop = kind => props.readOnly ? undefined : close => (() => {
732
+ var _el$14 = _$getNextElement(_tmpl$7),
733
+ _el$15 = _el$14.firstChild,
734
+ _el$16 = _el$15.nextSibling,
735
+ [_el$17, _co$4] = _$getNextMarker(_el$16.nextSibling);
736
+ _$insert(_el$15, () => menuHeading(kind()));
737
+ _$insert(_el$14, _$createComponent(Button, {
738
+ variant: "ghost",
739
+ onClick: close,
740
+ get children() {
741
+ return t().done;
742
+ }
743
+ }), _el$17, _co$4);
744
+ _$effect(() => _$setAttribute(_el$15, "title", menuHeading(kind())));
745
+ return _el$14;
746
+ })();
747
+ const menuLabel = () => props.readOnly ? t().copyTable : t().tableMenu;
748
+ const menuDisabled = () => menuBlocked() || localSource() || !cell()?.isConnected;
749
+ // A sheet owns its item inventory until it closes. Replace that owner when
667
750
  // edit access changes so an already open sheet cannot retain edit entries.
668
- _$createComponent(Show, {
751
+ /** The toolbar's table button: the whole menu for the focused cell. */
752
+ const menu = trigger => _$createComponent(Show, {
669
753
  get when() {
670
754
  return props.readOnly ? "read" : "write";
671
755
  },
672
756
  keyed: true,
673
757
  children: _access => _$createComponent(Dropdown, {
674
758
  get items() {
675
- return items();
759
+ return items("cell");
676
760
  },
677
761
  phone: "sheet",
678
762
  get sheetTop() {
679
- return props.readOnly ? undefined : close => (() => {
680
- var _el$16 = _$getNextElement(_tmpl$9),
681
- _el$17 = _el$16.firstChild,
682
- _el$18 = _el$17.nextSibling,
683
- [_el$19, _co$4] = _$getNextMarker(_el$18.nextSibling);
684
- _$insert(_el$17, menuHeading);
685
- _$insert(_el$16, _$createComponent(Button, {
686
- variant: "ghost",
687
- onClick: close,
688
- get children() {
689
- return t().done;
690
- }
691
- }), _el$19, _co$4);
692
- _$effect(() => _$setAttribute(_el$17, "title", menuHeading()));
693
- return _el$16;
694
- })();
763
+ return sheetTop(() => "cell");
695
764
  },
696
765
  menuClass: "k2b-ste-table-menu",
697
766
  get position() {
698
- return floating ? "right-start" : trigger?.position ?? "bottom-right";
767
+ return trigger?.position ?? "bottom-right";
699
768
  },
700
769
  width: "18rem",
701
770
  get label() {
702
- return _$memo(() => !!props.readOnly)() ? t().copyTable : t().tableMenu;
771
+ return menuLabel();
703
772
  },
704
773
  get disabled() {
705
- return menuBlocked() || localSource() || !cell()?.isConnected;
774
+ return menuDisabled();
706
775
  },
707
- onOpenChange: menuChange,
776
+ onOpenChange: tableMenuChange,
708
777
  onClose: menuClose,
709
778
  get children() {
710
779
  return _$createComponent(Dropdown.Trigger, {
711
780
  ref: element => {
712
- if (floating) floatingTrigger = element;else {
713
- tableTriggers.add(element);
714
- onCleanup(() => tableTriggers.delete(element));
715
- }
781
+ tableTriggers.add(element);
782
+ onCleanup(() => tableTriggers.delete(element));
716
783
  },
717
784
  get ["class"]() {
718
- return floating ? props.readOnly ? "k2b-ste-cell-menu k2b-ste-copy-menu" : "k2b-ste-cell-menu" : `k2b-ste-tool k2b-ste-table-tool ${trigger?.class ?? ""}`;
785
+ return `k2b-ste-tool k2b-ste-table-tool ${trigger?.class ?? ""}`;
719
786
  },
720
787
  variant: "ghost",
721
788
  get size() {
722
789
  return trigger?.size ?? "sm";
723
790
  },
724
791
  get iconOnly() {
725
- return _$memo(() => !!!trigger?.text)() && !props.readOnly;
792
+ return !trigger?.text;
726
793
  },
727
794
  get label() {
728
- return trigger?.text ?? (props.readOnly ? t().copyTable : t().tableMenu);
795
+ return trigger?.text ?? t().tableMenu;
729
796
  },
730
- "aria-pressed": !floating ? true : undefined,
797
+ "aria-pressed": true,
731
798
  onMouseDown: preserveSelection,
732
799
  get children() {
733
- return [(() => {
734
- var _el$14 = _$getNextElement(_tmpl$7);
735
- _$effect(() => _$className(_el$14, props.readOnly ? "ti ti-copy" : floating ? "ti ti-dots" : "ti ti-table"));
736
- return _el$14;
737
- })(), _$memo(() => trigger?.text ?? (props.readOnly ? t().copyTable : undefined)), _$createComponent(Show, {
800
+ return [_$getNextElement(_tmpl$2), _$memo(() => trigger?.text), _$createComponent(Show, {
738
801
  get when() {
739
802
  return trigger?.text;
740
803
  },
@@ -747,6 +810,63 @@ export function TextEditor(props) {
747
810
  }
748
811
  })
749
812
  });
813
+ /** Grips, right-click and Shift+F10 open this menu beside its anchor; it has no button of its own. */
814
+ const cellMenu = () => _$createComponent(Show, {
815
+ get when() {
816
+ return props.readOnly ? "read" : "write";
817
+ },
818
+ keyed: true,
819
+ children: _access => _$createComponent(Dropdown, {
820
+ get items() {
821
+ return items(scope());
822
+ },
823
+ phone: "sheet",
824
+ get sheetTop() {
825
+ return sheetTop(scope);
826
+ },
827
+ menuClass: "k2b-ste-table-menu",
828
+ position: "bottom-right",
829
+ width: "18rem",
830
+ get label() {
831
+ return menuLabel();
832
+ },
833
+ get disabled() {
834
+ return menuDisabled();
835
+ },
836
+ onOpenChange: tableMenuChange,
837
+ onClose: menuClose,
838
+ openAt: open => {
839
+ openCellMenu = open;
840
+ },
841
+ closeWith: close => {
842
+ closeCellMenu = close;
843
+ },
844
+ get children() {
845
+ return _$createComponent(Show, {
846
+ get when() {
847
+ return _$memo(() => !!(props.readOnly && cell() && mode() === "formatted"))() && !menuBlocked();
848
+ },
849
+ get children() {
850
+ return _$createComponent(Dropdown.Trigger, {
851
+ ref: element => {
852
+ copyTrigger = element;
853
+ },
854
+ "class": "k2b-ste-copy-menu",
855
+ variant: "ghost",
856
+ size: "sm",
857
+ get label() {
858
+ return t().copyTable;
859
+ },
860
+ onMouseDown: preserveSelection,
861
+ get children() {
862
+ return [_$getNextElement(_tmpl$9), _$memo(() => t().copyTable)];
863
+ }
864
+ });
865
+ }
866
+ });
867
+ }
868
+ })
869
+ });
750
870
  const runFormat = command => {
751
871
  if (!editor || blocked()) return;
752
872
  if (cell()?.isConnected && isInlineCommand(command) && formatActiveCell(editor.view, command)) return;
@@ -756,7 +876,7 @@ export function TextEditor(props) {
756
876
  if (disposed) return;
757
877
  // A host toolbar sits outside this root. Native popover focus moves before
758
878
  // its toggle callback, but Dropdown already exposes its synchronous state.
759
- if (menuOpen || [...tableTriggers, floatingTrigger].some(trigger => trigger?.isConnected && trigger.getAttribute("aria-expanded") === "true")) return;
879
+ if (menuOpen || [...tableTriggers, copyTrigger].some(trigger => trigger?.isConnected && trigger.getAttribute("aria-expanded") === "true")) return;
760
880
  const focused = document.activeElement;
761
881
  setLocalSource(focused instanceof HTMLElement && !!focused.closest(".k2b-ste-table-source"));
762
882
  const inside = focused instanceof Node && root.contains(focused);
@@ -809,9 +929,9 @@ export function TextEditor(props) {
809
929
  },
810
930
  onMouseDown: preserveSelection,
811
931
  get children() {
812
- var _el$20 = _$getNextElement(_tmpl$7);
813
- _$className(_el$20, `ti ti-${command}`);
814
- return _el$20;
932
+ var _el$21 = _$getNextElement(_tmpl$0);
933
+ _$className(_el$21, `ti ti-${command}`);
934
+ return _el$21;
815
935
  }
816
936
  });
817
937
  }
@@ -903,7 +1023,6 @@ export function TextEditor(props) {
903
1023
  const focus = event => {
904
1024
  if (!(event instanceof CustomEvent) || !(event.detail.cell instanceof HTMLElement)) return;
905
1025
  setActive(true);
906
- if (!menuOpen) pointerAnchor = undefined;
907
1026
  setCell(event.detail.cell);
908
1027
  positionMenu();
909
1028
  };
@@ -925,25 +1044,41 @@ export function TextEditor(props) {
925
1044
  pointerCheck = setTimeout(checkFocus);
926
1045
  }
927
1046
  };
1047
+ // A grip opens its row's or column's menu below itself; right-click opens
1048
+ // at the pointer and a key below the cell. Focus returns to the cell.
928
1049
  const keyboardMenu = event => {
929
- if (menuBlocked()) return;
930
- focus(event);
931
- if (event instanceof CustomEvent) {
932
- const bounds = root.getBoundingClientRect();
933
- const anchor = event.detail.anchor instanceof HTMLElement ? event.detail.anchor.getBoundingClientRect() : undefined;
934
- if (anchor || typeof event.detail.x === "number") {
935
- pointerAnchor = {
936
- left: `${(anchor?.left ?? event.detail.x) - bounds.left}px`,
937
- top: `${(anchor?.bottom ?? event.detail.y) - bounds.top}px`
938
- };
939
- positionMenu();
940
- }
1050
+ if (menuBlocked() || menuOpen || !(event instanceof CustomEvent) || !(event.detail.cell instanceof HTMLElement)) return;
1051
+ const target = event.detail.cell;
1052
+ const opener = event.detail.anchor instanceof HTMLElement ? event.detail.anchor : undefined;
1053
+ const kind = event.detail.scope === "row" || event.detail.scope === "column" ? event.detail.scope : "cell";
1054
+ const {
1055
+ x,
1056
+ y
1057
+ } = event.detail;
1058
+ let anchor = opener ?? target;
1059
+ if (!opener && typeof x === "number" && typeof y === "number") {
1060
+ // Keep the pointer's place inside the cell, so the menu follows it while the page scrolls.
1061
+ const start = target.getBoundingClientRect();
1062
+ anchor = {
1063
+ getBoundingClientRect: () => {
1064
+ const box = target.getBoundingClientRect();
1065
+ const left = box.left + x - start.left;
1066
+ const top = box.top + y - start.top;
1067
+ return {
1068
+ left,
1069
+ top,
1070
+ right: left,
1071
+ bottom: top
1072
+ };
1073
+ }
1074
+ };
941
1075
  }
1076
+ grip = opener;
1077
+ setScope(kind);
1078
+ focus(event);
1079
+ // Focus returns to the menu's cell as it closes; moving a row or column moves that cell.
942
1080
  queueMicrotask(() => {
943
- if (disposed || !cell()?.isConnected) return;
944
- const tableTrigger = [...tableTriggers].find(trigger => trigger.isConnected);
945
- const trigger = (window.matchMedia("(max-width: 47.999rem)").matches && tableTrigger?.isConnected ? tableTrigger : undefined) ?? floatingTrigger;
946
- trigger?.click();
1081
+ if (!disposed && cell()?.isConnected) openCellMenu?.(anchor, () => cell());
947
1082
  });
948
1083
  };
949
1084
  const mutations = new MutationObserver(schedulePosition);
@@ -962,15 +1097,12 @@ export function TextEditor(props) {
962
1097
  root.addEventListener("mousedown", viewMouseDown);
963
1098
  document.addEventListener("focusin", focusChange);
964
1099
  document.addEventListener("pointerdown", pointer);
1100
+ document.addEventListener("pointerdown", pressBesideMenu, true);
965
1101
  root.addEventListener("k2b-ste-table-structure", structureAnnouncement);
966
1102
  root.addEventListener("k2b-ste-cell-focus", focus);
967
1103
  root.addEventListener("k2b-ste-cell-blur", focusChange);
968
1104
  root.addEventListener("k2b-ste-cell-menu", keyboardMenu);
969
- const tableScroll = event => {
970
- if (menuOpen && event.target instanceof HTMLElement && event.target.classList.contains("k2b-ste-table") && floatingTrigger?.getAttribute("aria-expanded") === "true") floatingTrigger.click();
971
- schedulePosition();
972
- };
973
- window.addEventListener("scroll", tableScroll, true);
1105
+ window.addEventListener("scroll", schedulePosition, true);
974
1106
  window.addEventListener("resize", schedulePosition);
975
1107
  const viewport = window.visualViewport;
976
1108
  const keyboard = () => root.style.setProperty("--k2b-ste-keyboard", `${Math.max(0, window.innerHeight - (viewport?.height ?? window.innerHeight) - (viewport?.offsetTop ?? 0))}px`);
@@ -981,7 +1113,7 @@ export function TextEditor(props) {
981
1113
  updateCheck();
982
1114
  props.onReady?.(editor, {
983
1115
  tableActive: () => mode() === "formatted" && !!cell()?.isConnected,
984
- TableMenu: trigger => menu(false, trigger),
1116
+ TableMenu: trigger => menu(trigger),
985
1117
  formatCell: format => !!editor && !blocked() && !!cell()?.isConnected && formatActiveCell(editor.view, format),
986
1118
  notify: message => setNotice({
987
1119
  message
@@ -995,11 +1127,13 @@ export function TextEditor(props) {
995
1127
  root.removeEventListener("mousedown", viewMouseDown);
996
1128
  document.removeEventListener("focusin", focusChange);
997
1129
  document.removeEventListener("pointerdown", pointer);
1130
+ document.removeEventListener("pointerdown", pressBesideMenu, true);
1131
+ swallow?.abort();
998
1132
  root.removeEventListener("k2b-ste-table-structure", structureAnnouncement);
999
1133
  root.removeEventListener("k2b-ste-cell-focus", focus);
1000
1134
  root.removeEventListener("k2b-ste-cell-blur", focusChange);
1001
1135
  root.removeEventListener("k2b-ste-cell-menu", keyboardMenu);
1002
- window.removeEventListener("scroll", tableScroll, true);
1136
+ window.removeEventListener("scroll", schedulePosition, true);
1003
1137
  window.removeEventListener("resize", schedulePosition);
1004
1138
  viewport?.removeEventListener("resize", keyboard);
1005
1139
  viewport?.removeEventListener("scroll", keyboard);
@@ -1038,44 +1172,43 @@ export function TextEditor(props) {
1038
1172
  });
1039
1173
  });
1040
1174
  return (() => {
1041
- var _el$21 = _$getNextElement(_tmpl$15),
1042
- _el$22 = _el$21.firstChild,
1043
- _el$49 = _el$22.nextSibling,
1044
- [_el$50, _co$1] = _$getNextMarker(_el$49.nextSibling),
1045
- _el$51 = _el$50.nextSibling,
1046
- [_el$52, _co$10] = _$getNextMarker(_el$51.nextSibling),
1047
- _el$53 = _el$52.nextSibling,
1048
- [_el$54, _co$11] = _$getNextMarker(_el$53.nextSibling),
1049
- _el$33 = _el$54.nextSibling,
1050
- _el$55 = _el$33.nextSibling,
1051
- [_el$56, _co$12] = _$getNextMarker(_el$55.nextSibling),
1052
- _el$35 = _el$56.nextSibling;
1175
+ var _el$22 = _$getNextElement(_tmpl$15),
1176
+ _el$23 = _el$22.firstChild,
1177
+ _el$50 = _el$23.nextSibling,
1178
+ [_el$51, _co$1] = _$getNextMarker(_el$50.nextSibling),
1179
+ _el$52 = _el$51.nextSibling,
1180
+ [_el$53, _co$10] = _$getNextMarker(_el$52.nextSibling),
1181
+ _el$54 = _el$53.nextSibling,
1182
+ [_el$55, _co$11] = _$getNextMarker(_el$54.nextSibling),
1183
+ _el$34 = _el$55.nextSibling,
1184
+ _el$35 = _el$34.nextSibling,
1185
+ _el$36 = _el$35.nextSibling;
1053
1186
  var _ref$3 = root;
1054
- typeof _ref$3 === "function" ? _$use(_ref$3, _el$21) : root = _el$21;
1055
- _$insert(_el$22, announcement);
1056
- _$insert(_el$21, _$createComponent(Show, {
1187
+ typeof _ref$3 === "function" ? _$use(_ref$3, _el$22) : root = _el$22;
1188
+ _$insert(_el$23, announcement);
1189
+ _$insert(_el$22, _$createComponent(Show, {
1057
1190
  get when() {
1058
1191
  return !props.presentation || "status" in props || check();
1059
1192
  },
1060
1193
  get children() {
1061
- var _el$23 = _$getNextElement(_tmpl$1),
1062
- _el$24 = _el$23.firstChild,
1063
- _el$29 = _el$24.nextSibling,
1064
- [_el$30, _co$6] = _$getNextMarker(_el$29.nextSibling);
1065
- _$insert(_el$24, () => props.status);
1066
- _$insert(_el$23, _$createComponent(Show, {
1194
+ var _el$24 = _$getNextElement(_tmpl$10),
1195
+ _el$25 = _el$24.firstChild,
1196
+ _el$30 = _el$25.nextSibling,
1197
+ [_el$31, _co$6] = _$getNextMarker(_el$30.nextSibling);
1198
+ _$insert(_el$25, () => props.status);
1199
+ _$insert(_el$24, _$createComponent(Show, {
1067
1200
  get when() {
1068
1201
  return check();
1069
1202
  },
1070
1203
  get children() {
1071
1204
  return [(() => {
1072
- var _el$25 = _$getNextElement(_tmpl$0),
1073
- _el$26 = _el$25.firstChild,
1074
- _el$27 = _el$26.nextSibling,
1075
- [_el$28, _co$5] = _$getNextMarker(_el$27.nextSibling);
1076
- _$insert(_el$25, () => t().checkTable, _el$28, _co$5);
1077
- _$effect(() => _$setAttribute(_el$25, "title", missingSeparator() ? t().checkSeparatorDetail : t().checkTableDetail));
1078
- return _el$25;
1205
+ var _el$26 = _$getNextElement(_tmpl$1),
1206
+ _el$27 = _el$26.firstChild,
1207
+ _el$28 = _el$27.nextSibling,
1208
+ [_el$29, _co$5] = _$getNextMarker(_el$28.nextSibling);
1209
+ _$insert(_el$26, () => t().checkTable, _el$29, _co$5);
1210
+ _$effect(() => _$setAttribute(_el$26, "title", missingSeparator() ? t().checkSeparatorDetail : t().checkTableDetail));
1211
+ return _el$26;
1079
1212
  })(), _$createComponent(Button, {
1080
1213
  size: "sm",
1081
1214
  variant: "ghost",
@@ -1104,17 +1237,17 @@ export function TextEditor(props) {
1104
1237
  }
1105
1238
  })];
1106
1239
  }
1107
- }), _el$30, _co$6);
1108
- return _el$23;
1240
+ }), _el$31, _co$6);
1241
+ return _el$24;
1109
1242
  }
1110
- }), _el$50, _co$1);
1111
- _$insert(_el$21, _$createComponent(Show, {
1243
+ }), _el$51, _co$1);
1244
+ _$insert(_el$22, _$createComponent(Show, {
1112
1245
  get when() {
1113
1246
  return _$memo(() => !!(!props.readOnly && mode() !== "plain"))() && props.toolbar !== false;
1114
1247
  },
1115
1248
  get children() {
1116
- var _el$31 = _$getNextElement(_tmpl$10);
1117
- _$insert(_el$31, _$createComponent(Toolbar, {
1249
+ var _el$32 = _$getNextElement(_tmpl$11);
1250
+ _$insert(_el$32, _$createComponent(Toolbar, {
1118
1251
  "class": "k2b-ste-toolbar",
1119
1252
  get label() {
1120
1253
  return t().formatting;
@@ -1153,14 +1286,14 @@ export function TextEditor(props) {
1153
1286
  onMouseDown: preserveSelection,
1154
1287
  onClick: () => runFormat(command),
1155
1288
  get children() {
1156
- var _el$57 = _$getNextElement(_tmpl$7);
1157
- _$className(_el$57, `ti ti-${icon}`);
1158
- return _el$57;
1289
+ var _el$56 = _$getNextElement(_tmpl$0);
1290
+ _$className(_el$56, `ti ti-${icon}`);
1291
+ return _el$56;
1159
1292
  }
1160
1293
  });
1161
1294
  },
1162
1295
  get children() {
1163
- return menu(false);
1296
+ return menu();
1164
1297
  }
1165
1298
  })
1166
1299
  });
@@ -1190,64 +1323,45 @@ export function TextEditor(props) {
1190
1323
  })];
1191
1324
  }
1192
1325
  }));
1193
- return _el$31;
1326
+ return _el$32;
1194
1327
  }
1195
- }), _el$52, _co$10);
1196
- _$insert(_el$21, _$createComponent(Show, {
1328
+ }), _el$53, _co$10);
1329
+ _$insert(_el$22, _$createComponent(Show, {
1197
1330
  get when() {
1198
1331
  return props.loading || !ready();
1199
1332
  },
1200
1333
  get children() {
1201
- var _el$32 = _$getNextElement(_tmpl$11);
1202
- _$insert(_el$32, _$createComponent(For, {
1334
+ var _el$33 = _$getNextElement(_tmpl$12);
1335
+ _$insert(_el$33, _$createComponent(For, {
1203
1336
  each: [82, 100, 68, 0, 96, 74],
1204
1337
  children: width => (() => {
1205
- var _el$58 = _$getNextElement(_tmpl$16),
1206
- _el$59 = _el$58.firstChild;
1207
- _$setStyleProperty(_el$59, "width", `${width}%`);
1208
- return _el$58;
1338
+ var _el$57 = _$getNextElement(_tmpl$16),
1339
+ _el$58 = _el$57.firstChild;
1340
+ _$setStyleProperty(_el$58, "width", `${width}%`);
1341
+ return _el$57;
1209
1342
  })()
1210
1343
  }));
1211
- _$effect(() => _$setAttribute(_el$32, "aria-label", t().loading));
1212
- return _el$32;
1344
+ _$effect(() => _$setAttribute(_el$33, "aria-label", t().loading));
1345
+ return _el$33;
1213
1346
  }
1214
- }), _el$54, _co$11);
1347
+ }), _el$55, _co$11);
1215
1348
  var _ref$4 = mount;
1216
- typeof _ref$4 === "function" ? _$use(_ref$4, _el$33) : mount = _el$33;
1217
- _$insert(_el$21, _$createComponent(Show, {
1218
- get when() {
1219
- return _$memo(() => !!(cell() && mode() === "formatted"))() && !menuBlocked();
1220
- },
1221
- get children() {
1222
- var _el$34 = _$getNextElement(_tmpl$12);
1223
- _$insert(_el$34, () => menu(true));
1224
- _$effect(_p$ => {
1225
- var _v$5 = props.readOnly ? "true" : undefined,
1226
- _v$6 = menuPosition();
1227
- _v$5 !== _p$.e && _$setAttribute(_el$34, "data-readonly", _p$.e = _v$5);
1228
- _p$.t = _$style(_el$34, _v$6, _p$.t);
1229
- return _p$;
1230
- }, {
1231
- e: undefined,
1232
- t: undefined
1233
- });
1234
- return _el$34;
1235
- }
1236
- }), _el$56, _co$12);
1237
- _$insert(_el$35, _$createComponent(Show, {
1349
+ typeof _ref$4 === "function" ? _$use(_ref$4, _el$34) : mount = _el$34;
1350
+ _$insert(_el$35, cellMenu);
1351
+ _$insert(_el$36, _$createComponent(Show, {
1238
1352
  get when() {
1239
1353
  return notice() || recovery() !== undefined;
1240
1354
  },
1241
1355
  get children() {
1242
- var _el$36 = _$getNextElement(_tmpl$14),
1243
- _el$37 = _el$36.firstChild,
1244
- _el$42 = _el$37.firstChild,
1245
- [_el$43, _co$8] = _$getNextMarker(_el$42.nextSibling),
1246
- _el$44 = _el$43.nextSibling,
1247
- [_el$45, _co$9] = _$getNextMarker(_el$44.nextSibling),
1248
- _el$47 = _el$37.nextSibling,
1249
- [_el$48, _co$0] = _$getNextMarker(_el$47.nextSibling);
1250
- _$insert(_el$37, _$createComponent(Show, {
1356
+ var _el$37 = _$getNextElement(_tmpl$14),
1357
+ _el$38 = _el$37.firstChild,
1358
+ _el$43 = _el$38.firstChild,
1359
+ [_el$44, _co$8] = _$getNextMarker(_el$43.nextSibling),
1360
+ _el$45 = _el$44.nextSibling,
1361
+ [_el$46, _co$9] = _$getNextMarker(_el$45.nextSibling),
1362
+ _el$48 = _el$38.nextSibling,
1363
+ [_el$49, _co$0] = _$getNextMarker(_el$48.nextSibling);
1364
+ _$insert(_el$38, _$createComponent(Show, {
1251
1365
  get when() {
1252
1366
  return notice();
1253
1367
  },
@@ -1258,9 +1372,9 @@ export function TextEditor(props) {
1258
1372
  },
1259
1373
  get fallback() {
1260
1374
  return (() => {
1261
- var _el$60 = _$getNextElement(_tmpl$17);
1262
- _$insert(_el$60, () => notice()?.message);
1263
- return _el$60;
1375
+ var _el$59 = _$getNextElement(_tmpl$17);
1376
+ _$insert(_el$59, () => notice()?.message);
1377
+ return _el$59;
1264
1378
  })();
1265
1379
  },
1266
1380
  get children() {
@@ -1278,20 +1392,20 @@ export function TextEditor(props) {
1278
1392
  }
1279
1393
  });
1280
1394
  }
1281
- }), _el$43, _co$8);
1282
- _$insert(_el$37, _$createComponent(Show, {
1395
+ }), _el$44, _co$8);
1396
+ _$insert(_el$38, _$createComponent(Show, {
1283
1397
  get when() {
1284
1398
  return recovery() !== undefined;
1285
1399
  },
1286
1400
  get children() {
1287
1401
  return [(() => {
1288
- var _el$38 = _$getNextElement(_tmpl$6),
1289
- _el$40 = _el$38.firstChild,
1290
- [_el$41, _co$7] = _$getNextMarker(_el$40.nextSibling),
1291
- _el$39 = _el$41.nextSibling;
1292
- _$insert(_el$38, () => t().recoveredInput, _el$41, _co$7);
1293
- _$effect(() => _$setProperty(_el$39, "value", recovery()));
1294
- return _el$38;
1402
+ var _el$39 = _$getNextElement(_tmpl$6),
1403
+ _el$41 = _el$39.firstChild,
1404
+ [_el$42, _co$7] = _$getNextMarker(_el$41.nextSibling),
1405
+ _el$40 = _el$42.nextSibling;
1406
+ _$insert(_el$39, () => t().recoveredInput, _el$42, _co$7);
1407
+ _$effect(() => _$setProperty(_el$40, "value", recovery()));
1408
+ return _el$39;
1295
1409
  })(), _$createComponent(Button, {
1296
1410
  size: "sm",
1297
1411
  variant: "ghost",
@@ -1304,8 +1418,8 @@ export function TextEditor(props) {
1304
1418
  }
1305
1419
  })];
1306
1420
  }
1307
- }), _el$45, _co$9);
1308
- _$insert(_el$36, _$createComponent(Show, {
1421
+ }), _el$46, _co$9);
1422
+ _$insert(_el$37, _$createComponent(Show, {
1309
1423
  get when() {
1310
1424
  return notice();
1311
1425
  },
@@ -1322,25 +1436,27 @@ export function TextEditor(props) {
1322
1436
  }
1323
1437
  });
1324
1438
  }
1325
- }), _el$48, _co$0);
1326
- return _el$36;
1439
+ }), _el$49, _co$0);
1440
+ return _el$37;
1327
1441
  }
1328
1442
  }));
1329
1443
  _$effect(_p$ => {
1330
- var _v$7 = `k2b-text-editor ${props.class ?? ""}`,
1331
- _v$8 = mode(),
1332
- _v$9 = props.readOnly ? "true" : undefined,
1333
- _v$0 = active() ? "true" : undefined,
1444
+ var _v$5 = `k2b-text-editor ${props.class ?? ""}`,
1445
+ _v$6 = mode(),
1446
+ _v$7 = props.readOnly ? "true" : undefined,
1447
+ _v$8 = active() ? "true" : undefined,
1448
+ _v$9 = props.loading || !ready(),
1449
+ _v$0 = props.presentation ? "k2b-ste-app-host" : "k2b-ste-host",
1334
1450
  _v$1 = props.loading || !ready(),
1335
- _v$10 = props.presentation ? "k2b-ste-app-host" : "k2b-ste-host",
1336
- _v$11 = props.loading || !ready();
1337
- _v$7 !== _p$.e && _$className(_el$21, _p$.e = _v$7);
1338
- _v$8 !== _p$.t && _$setAttribute(_el$21, "data-mode", _p$.t = _v$8);
1339
- _v$9 !== _p$.a && _$setAttribute(_el$21, "data-readonly", _p$.a = _v$9);
1340
- _v$0 !== _p$.o && _$setAttribute(_el$21, "data-active", _p$.o = _v$0);
1341
- _v$1 !== _p$.i && _$setAttribute(_el$21, "aria-busy", _p$.i = _v$1);
1342
- _v$10 !== _p$.n && _$className(_el$33, _p$.n = _v$10);
1343
- _v$11 !== _p$.s && _$setProperty(_el$33, "hidden", _p$.s = _v$11);
1451
+ _v$10 = menuPosition();
1452
+ _v$5 !== _p$.e && _$className(_el$22, _p$.e = _v$5);
1453
+ _v$6 !== _p$.t && _$setAttribute(_el$22, "data-mode", _p$.t = _v$6);
1454
+ _v$7 !== _p$.a && _$setAttribute(_el$22, "data-readonly", _p$.a = _v$7);
1455
+ _v$8 !== _p$.o && _$setAttribute(_el$22, "data-active", _p$.o = _v$8);
1456
+ _v$9 !== _p$.i && _$setAttribute(_el$22, "aria-busy", _p$.i = _v$9);
1457
+ _v$0 !== _p$.n && _$className(_el$34, _p$.n = _v$0);
1458
+ _v$1 !== _p$.s && _$setProperty(_el$34, "hidden", _p$.s = _v$1);
1459
+ _p$.h = _$style(_el$35, _v$10, _p$.h);
1344
1460
  return _p$;
1345
1461
  }, {
1346
1462
  e: undefined,
@@ -1349,9 +1465,10 @@ export function TextEditor(props) {
1349
1465
  o: undefined,
1350
1466
  i: undefined,
1351
1467
  n: undefined,
1352
- s: undefined
1468
+ s: undefined,
1469
+ h: undefined
1353
1470
  });
1354
- return _el$21;
1471
+ return _el$22;
1355
1472
  })();
1356
1473
  }
1357
1474
  _$delegateEvents(["keydown", "click"]);