@k2b/ui 0.38.1 → 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
@@ -11,20 +11,19 @@ var _tmpl$ = ["<div class=\"k2b-ste-size-picker\">", "", "</div>"],
11
11
  _tmpl$4 = ["<div class=\"k2b-ste-size-row\">", "</div>"],
12
12
  _tmpl$5 = ["<button type=\"button\" tabindex=\"-1\"", "></button>"],
13
13
  _tmpl$6 = ["<label class=\"k2b-ste-recovery-label\">", "<textarea class=\"k2b-ste-recovery\" readonly", "></textarea></label>"],
14
- _tmpl$7 = ["<i", " aria-hidden=\"true\"></i>"],
14
+ _tmpl$7 = ["<div class=\"k2b-ste-table-sheet-heading\"><span", ">", "</span>", "</div>"],
15
15
  _tmpl$8 = "<i class=\"ti ti-chevron-down\" aria-hidden=\"true\"></i>",
16
- _tmpl$9 = ["<div class=\"k2b-ste-table-sheet-heading\"><span", ">", "</span>", "</div>"],
16
+ _tmpl$9 = "<i class=\"ti ti-copy\" aria-hidden=\"true\"></i>",
17
17
  _tmpl$0 = ["<i class=\"", "\" aria-hidden=\"true\"></i>"],
18
18
  _tmpl$1 = ["<span class=\"k2b-ste-check\"", "><i class=\"ti ti-alert-triangle\" aria-hidden=\"true\"></i>", "</span>"],
19
19
  _tmpl$10 = ["<div class=\"k2b-ste-status\" role=\"status\"><span>", "</span>", "</div>"],
20
20
  _tmpl$11 = ["<div class=\"k2b-ste-toolbar-slot\">", "</div>"],
21
21
  _tmpl$12 = ["<div class=\"k2b-ste-loading\" role=\"status\"", ">", "</div>"],
22
- _tmpl$13 = ["<div class=\"k2b-ste-floating-menu\"", " style=\"", "\">", "</div>"],
23
- _tmpl$14 = "<i class=\"ti ti-x\" aria-hidden=\"true\"></i>",
24
- _tmpl$15 = ["<div class=\"k2b-ste-notice\"><div class=\"k2b-ste-notice-content\">", "", "</div>", "</div>"],
25
- _tmpl$16 = ["<div class=\"", "\"", "><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\">", "</div></div>"],
26
- _tmpl$17 = ["<div><span style=\"", "\"></span></div>"],
27
- _tmpl$18 = ["<span>", "</span>"];
22
+ _tmpl$13 = "<i class=\"ti ti-x\" aria-hidden=\"true\"></i>",
23
+ _tmpl$14 = ["<div class=\"k2b-ste-notice\"><div class=\"k2b-ste-notice-content\">", "", "</div>", "</div>"],
24
+ _tmpl$15 = ["<div class=\"", "\"", "><span class=\"k2b-ste-announcement\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">", "</span>", "", "", "<div", "", "></div><div class=\"k2b-ste-floating-menu\" style=\"", "\">", "</div><div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">", "</div></div>"],
25
+ _tmpl$16 = ["<div><span style=\"", "\"></span></div>"],
26
+ _tmpl$17 = ["<span>", "</span>"];
28
27
  import { history, historyKeymap, redo, undo } from "@codemirror/commands";
29
28
  import { EditorView, keymap } from "@codemirror/view";
30
29
  import { clipboard } from "@k2b/stdlib/solid";
@@ -279,10 +278,27 @@ export function TextEditor(props) {
279
278
  const [documentRevision, setDocumentRevision] = createSignal(0);
280
279
  const [phoneMenu, setPhoneMenu] = createSignal(false);
281
280
  const [platform, setPlatform] = createSignal("other");
281
+ /** What the open cell menu acts on: a row grip, a column grip, or the cell (right-click, keys, toolbar). */
282
+ const [scope, setScope] = createSignal("cell");
283
+ let grip;
282
284
  createEffect(() => {
283
285
  const target = cell();
284
- target?.setAttribute("data-active", "");
285
- onCleanup(() => target?.removeAttribute("data-active"));
286
+ const kind = scope();
287
+ if (!target) return;
288
+ if (kind === "cell") {
289
+ target.setAttribute("data-active", "");
290
+ onCleanup(() => target.removeAttribute("data-active"));
291
+ return;
292
+ }
293
+ // A grip's menu marks its whole row or column, and the grip stays visible while the menu is open.
294
+ 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);
295
+ const opener = grip;
296
+ for (const element of marked) element?.setAttribute("data-marked", "");
297
+ opener?.setAttribute("data-open", "");
298
+ onCleanup(() => {
299
+ for (const element of marked) element?.removeAttribute("data-marked");
300
+ opener?.removeAttribute("data-open");
301
+ });
286
302
  });
287
303
  let disposed = false;
288
304
  onCleanup(() => {
@@ -344,8 +360,9 @@ export function TextEditor(props) {
344
360
  show();
345
361
  };
346
362
  let menuOpen = false;
347
- let pointerAnchor;
348
- let floatingTrigger;
363
+ let openCellMenu;
364
+ let closeCellMenu;
365
+ let copyTrigger;
349
366
  const tableTriggers = new Set();
350
367
  let frame = 0;
351
368
  const [menuPosition, setMenuPosition] = createSignal({
@@ -373,22 +390,19 @@ export function TextEditor(props) {
373
390
  const wrap = target.closest(".k2b-ste-table");
374
391
  const table = wrap?.querySelector("table");
375
392
  if (!target.isConnected || !wrap || !table || mode() !== "formatted") {
393
+ closeCellMenu?.();
376
394
  menuOpen = false;
377
395
  setCell(undefined);
378
396
  checkFocus();
379
397
  return;
380
398
  }
381
- const rect = target.getBoundingClientRect();
399
+ // Only the read-only phone copy control sits below its table; every other menu opens beside its anchor.
400
+ if (!props.readOnly) return;
382
401
  const bounds = root.getBoundingClientRect();
383
- const right = Math.min(table.getBoundingClientRect().right, wrap.getBoundingClientRect().right);
384
- const position = props.readOnly ? {
402
+ setMenuPosition({
385
403
  left: `${wrap.getBoundingClientRect().left - bounds.left}px`,
386
404
  top: `${wrap.getBoundingClientRect().bottom - bounds.top - (window.matchMedia("(max-width: 47.999rem)").matches ? 44 : 0)}px`
387
- } : {
388
- left: `${right - bounds.left - 24}px`,
389
- top: `${rect.top - bounds.top + (rect.height - 24) / 2}px`
390
- };
391
- setMenuPosition(pointerAnchor ?? position);
405
+ });
392
406
  };
393
407
  const schedulePosition = () => {
394
408
  if (!frame) frame = requestAnimationFrame(() => {
@@ -450,10 +464,12 @@ export function TextEditor(props) {
450
464
  const header = cell()?.closest(".k2b-ste-table")?.querySelector(`.k2b-ste-cell[data-r="0"][data-c="${column}"]`);
451
465
  return t().menuColumn(header?.dataset.raw || t().column(column + 1));
452
466
  };
453
- const menuHeading = () => `${menuRow()} · ${menuColumn()}`;
454
- const actionItem = (action, choice) => ({
467
+ const menuHeading = (kind = scope()) => kind === "row" ? menuRow() : kind === "column" ? menuColumn() : `${menuRow()} · ${menuColumn()}`;
468
+ const actionItem = (action, choice, kind = "cell") => ({
455
469
  get label() {
456
- return phoneMenu() && action in shortActionLabels ? t()[shortActionLabels[action]] : t()[action];
470
+ // A grip's sheet has no row or column heading over its entries, so deleting keeps its full name.
471
+ const short = phoneMenu() && action in shortActionLabels && !(kind !== "cell" && action.startsWith("delete"));
472
+ return short ? t()[shortActionLabels[action]] : t()[action];
457
473
  },
458
474
  icon: `ti ti-${actionIcons[action]}`,
459
475
  get disabled() {
@@ -523,25 +539,22 @@ export function TextEditor(props) {
523
539
  void copy("markdown");
524
540
  }
525
541
  }];
526
- const items = () => {
542
+ /** The row grip shows row and table actions, the column grip column, alignment and table actions; the rest everything. */
543
+ const items = kind => {
527
544
  if (props.readOnly) return copyItems();
528
545
  const align = () => {
529
546
  documentRevision();
530
547
  return cell()?.parentElement?.style.textAlign || "left";
531
548
  };
532
- return [{
549
+ const row = [{
533
550
  get sectionLabel() {
534
- return menuHeading();
551
+ return kind === "cell" ? menuRow() : undefined;
535
552
  },
536
- items: []
537
- }, {
538
- get sectionLabel() {
539
- return menuRow();
540
- },
541
- items: ["rowAbove", "rowBelow", "moveRowUp", "moveRowDown", "deleteRow"].map(action => actionItem(action))
542
- }, {
553
+ items: ["rowAbove", "rowBelow", "moveRowUp", "moveRowDown", "deleteRow"].map(action => actionItem(action, undefined, kind))
554
+ }];
555
+ const column = [{
543
556
  get sectionLabel() {
544
- return menuColumn();
557
+ return kind === "cell" ? menuColumn() : undefined;
545
558
  },
546
559
  items: ["colLeft", "colRight", "moveColLeft", "moveColRight"].map(action => actionItem(action))
547
560
  }, {
@@ -554,8 +567,14 @@ export function TextEditor(props) {
554
567
  checked: () => align() === ["left", "center", "right"][index]
555
568
  }))
556
569
  }, {
557
- items: [actionItem("deleteCol")]
558
- }, {
570
+ items: [actionItem("deleteCol", undefined, kind)]
571
+ }];
572
+ return [{
573
+ get sectionLabel() {
574
+ return menuHeading(kind);
575
+ },
576
+ items: []
577
+ }, ...(kind === "column" ? [] : row), ...(kind === "row" ? [] : column), {
559
578
  sectionLabel: t().table,
560
579
  items: [actionItem("toggleHeader", {
561
580
  choice: "checkbox",
@@ -581,70 +600,119 @@ export function TextEditor(props) {
581
600
  const menuChange = open => {
582
601
  menuOpen = open;
583
602
  };
603
+ // A press beside an open table menu only closes it: no cell takes focus, no
604
+ // text is selected and no column is marked. The menu's light dismiss closes it.
605
+ let tableMenuOpen = false;
606
+ const tableMenuChange = open => {
607
+ menuOpen = open;
608
+ tableMenuOpen = open && !phoneMenu();
609
+ };
610
+ let swallow;
611
+ const pressBesideMenu = event => {
612
+ swallow?.abort();
613
+ if (!tableMenuOpen || event.target instanceof Element && event.target.closest(".k2b-ste-table-menu")) return;
614
+ event.stopPropagation();
615
+ const current = new AbortController();
616
+ swallow = current;
617
+ const block = blocked => {
618
+ blocked.preventDefault();
619
+ blocked.stopPropagation();
620
+ if (blocked.type === "click" || blocked.type === "contextmenu") current.abort();
621
+ };
622
+ for (const type of ["mousedown", "mouseup", "click", "contextmenu"]) document.addEventListener(type, block, {
623
+ capture: true,
624
+ signal: current.signal
625
+ });
626
+ // Only this press is swallowed: its click follows the release in the same task, and a cancelled press never
627
+ // clicks. A key other than a modifier, which may come with the press, starts new input.
628
+ const end = () => current.abort();
629
+ document.addEventListener("pointerup", () => setTimeout(end), {
630
+ capture: true,
631
+ once: true,
632
+ signal: current.signal
633
+ });
634
+ document.addEventListener("pointercancel", end, {
635
+ capture: true,
636
+ once: true,
637
+ signal: current.signal
638
+ });
639
+ document.addEventListener("keydown", key => {
640
+ if (!["Shift", "Control", "Alt", "Meta"].includes(key.key)) end();
641
+ }, {
642
+ capture: true,
643
+ signal: current.signal
644
+ });
645
+ };
584
646
  const menuClose = () => {
585
647
  menuOpen = false;
586
- pointerAnchor = undefined;
648
+ tableMenuOpen = false;
649
+ setScope("cell");
650
+ grip = undefined;
587
651
  schedulePosition();
588
652
  queueMicrotask(() => {
589
653
  if (disposed) return;
590
654
  const focused = document.activeElement;
591
- if (focused instanceof HTMLElement && focused.matches(".k2b-ste-cell-menu, .k2b-ste-table-tool")) cell()?.focus();
655
+ if (focused instanceof HTMLElement && focused.matches(".k2b-ste-copy-menu, .k2b-ste-table-tool")) cell()?.focus();
592
656
  checkFocus();
593
657
  });
594
658
  };
595
- const menu = (floating, trigger) => // A sheet owns its item inventory until it closes. Replace that owner when
659
+ const sheetTop = kind => props.readOnly ? undefined : close => _$ssr(_tmpl$7, _$ssrAttribute("title", _$escape(menuHeading(kind()), true), false), _$escape(menuHeading(kind())), _$escape(_$createComponent(Button, {
660
+ variant: "ghost",
661
+ onClick: close,
662
+ get children() {
663
+ return t().done;
664
+ }
665
+ })));
666
+ const menuLabel = () => props.readOnly ? t().copyTable : t().tableMenu;
667
+ const menuDisabled = () => menuBlocked() || localSource() || !cell()?.isConnected;
668
+ // A sheet owns its item inventory until it closes. Replace that owner when
596
669
  // edit access changes so an already open sheet cannot retain edit entries.
597
- _$createComponent(Show, {
670
+ /** The toolbar's table button: the whole menu for the focused cell. */
671
+ const menu = trigger => _$createComponent(Show, {
598
672
  get when() {
599
673
  return props.readOnly ? "read" : "write";
600
674
  },
601
675
  keyed: true,
602
676
  children: _access => _$createComponent(Dropdown, {
603
677
  get items() {
604
- return items();
678
+ return items("cell");
605
679
  },
606
680
  phone: "sheet",
607
681
  get sheetTop() {
608
- return props.readOnly ? undefined : close => _$ssr(_tmpl$9, _$ssrAttribute("title", _$escape(menuHeading(), true), false), _$escape(menuHeading()), _$escape(_$createComponent(Button, {
609
- variant: "ghost",
610
- onClick: close,
611
- get children() {
612
- return t().done;
613
- }
614
- })));
682
+ return sheetTop(() => "cell");
615
683
  },
616
684
  menuClass: "k2b-ste-table-menu",
617
685
  get position() {
618
- return floating ? "right-start" : trigger?.position ?? "bottom-right";
686
+ return trigger?.position ?? "bottom-right";
619
687
  },
620
688
  width: "18rem",
621
689
  get label() {
622
- return _$memo(() => !!props.readOnly)() ? t().copyTable : t().tableMenu;
690
+ return menuLabel();
623
691
  },
624
692
  get disabled() {
625
- return menuBlocked() || localSource() || !cell()?.isConnected;
693
+ return menuDisabled();
626
694
  },
627
- onOpenChange: menuChange,
695
+ onOpenChange: tableMenuChange,
628
696
  onClose: menuClose,
629
697
  get children() {
630
698
  return _$createComponent(Dropdown.Trigger, {
631
699
  get ["class"]() {
632
- 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 ?? ""}`;
700
+ return `k2b-ste-tool k2b-ste-table-tool ${trigger?.class ?? ""}`;
633
701
  },
634
702
  variant: "ghost",
635
703
  get size() {
636
704
  return trigger?.size ?? "sm";
637
705
  },
638
706
  get iconOnly() {
639
- return _$memo(() => !!!trigger?.text)() && !props.readOnly;
707
+ return !trigger?.text;
640
708
  },
641
709
  get label() {
642
- return trigger?.text ?? (props.readOnly ? t().copyTable : t().tableMenu);
710
+ return trigger?.text ?? t().tableMenu;
643
711
  },
644
- "aria-pressed": !floating ? true : undefined,
712
+ "aria-pressed": true,
645
713
  onMouseDown: preserveSelection,
646
714
  get children() {
647
- return [_$ssr(_tmpl$7, _$ssrAttribute("class", props.readOnly ? "ti ti-copy" : floating ? "ti ti-dots" : "ti ti-table", false)), trigger?.text ?? (props.readOnly ? t().copyTable : undefined), _$createComponent(Show, {
715
+ return [_$ssr(_tmpl$2), trigger?.text, _$createComponent(Show, {
648
716
  get when() {
649
717
  return trigger?.text;
650
718
  },
@@ -657,6 +725,60 @@ export function TextEditor(props) {
657
725
  }
658
726
  })
659
727
  });
728
+ /** Grips, right-click and Shift+F10 open this menu beside its anchor; it has no button of its own. */
729
+ const cellMenu = () => _$createComponent(Show, {
730
+ get when() {
731
+ return props.readOnly ? "read" : "write";
732
+ },
733
+ keyed: true,
734
+ children: _access => _$createComponent(Dropdown, {
735
+ get items() {
736
+ return items(scope());
737
+ },
738
+ phone: "sheet",
739
+ get sheetTop() {
740
+ return sheetTop(scope);
741
+ },
742
+ menuClass: "k2b-ste-table-menu",
743
+ position: "bottom-right",
744
+ width: "18rem",
745
+ get label() {
746
+ return menuLabel();
747
+ },
748
+ get disabled() {
749
+ return menuDisabled();
750
+ },
751
+ onOpenChange: tableMenuChange,
752
+ onClose: menuClose,
753
+ openAt: open => {
754
+ openCellMenu = open;
755
+ },
756
+ closeWith: close => {
757
+ closeCellMenu = close;
758
+ },
759
+ get children() {
760
+ return _$createComponent(Show, {
761
+ get when() {
762
+ return _$memo(() => !!(props.readOnly && cell() && mode() === "formatted"))() && !menuBlocked();
763
+ },
764
+ get children() {
765
+ return _$createComponent(Dropdown.Trigger, {
766
+ "class": "k2b-ste-copy-menu",
767
+ variant: "ghost",
768
+ size: "sm",
769
+ get label() {
770
+ return t().copyTable;
771
+ },
772
+ onMouseDown: preserveSelection,
773
+ get children() {
774
+ return [_$ssr(_tmpl$9), t().copyTable];
775
+ }
776
+ });
777
+ }
778
+ });
779
+ }
780
+ })
781
+ });
660
782
  const runFormat = command => {
661
783
  if (!editor || blocked()) return;
662
784
  if (cell()?.isConnected && isInlineCommand(command) && formatActiveCell(editor.view, command)) return;
@@ -666,7 +788,7 @@ export function TextEditor(props) {
666
788
  if (disposed) return;
667
789
  // A host toolbar sits outside this root. Native popover focus moves before
668
790
  // its toggle callback, but Dropdown already exposes its synchronous state.
669
- if (menuOpen || [...tableTriggers, floatingTrigger].some(trigger => trigger?.isConnected && trigger.getAttribute("aria-expanded") === "true")) return;
791
+ if (menuOpen || [...tableTriggers, copyTrigger].some(trigger => trigger?.isConnected && trigger.getAttribute("aria-expanded") === "true")) return;
670
792
  const focused = document.activeElement;
671
793
  setLocalSource(focused instanceof HTMLElement && !!focused.closest(".k2b-ste-table-source"));
672
794
  const inside = focused instanceof Node && root.contains(focused);
@@ -811,7 +933,6 @@ export function TextEditor(props) {
811
933
  const focus = event => {
812
934
  if (!(event instanceof CustomEvent) || !(event.detail.cell instanceof HTMLElement)) return;
813
935
  setActive(true);
814
- if (!menuOpen) pointerAnchor = undefined;
815
936
  setCell(event.detail.cell);
816
937
  positionMenu();
817
938
  };
@@ -833,25 +954,41 @@ export function TextEditor(props) {
833
954
  pointerCheck = setTimeout(checkFocus);
834
955
  }
835
956
  };
957
+ // A grip opens its row's or column's menu below itself; right-click opens
958
+ // at the pointer and a key below the cell. Focus returns to the cell.
836
959
  const keyboardMenu = event => {
837
- if (menuBlocked()) return;
838
- focus(event);
839
- if (event instanceof CustomEvent) {
840
- const bounds = root.getBoundingClientRect();
841
- const anchor = event.detail.anchor instanceof HTMLElement ? event.detail.anchor.getBoundingClientRect() : undefined;
842
- if (anchor || typeof event.detail.x === "number") {
843
- pointerAnchor = {
844
- left: `${(anchor?.left ?? event.detail.x) - bounds.left}px`,
845
- top: `${(anchor?.bottom ?? event.detail.y) - bounds.top}px`
846
- };
847
- positionMenu();
848
- }
960
+ if (menuBlocked() || menuOpen || !(event instanceof CustomEvent) || !(event.detail.cell instanceof HTMLElement)) return;
961
+ const target = event.detail.cell;
962
+ const opener = event.detail.anchor instanceof HTMLElement ? event.detail.anchor : undefined;
963
+ const kind = event.detail.scope === "row" || event.detail.scope === "column" ? event.detail.scope : "cell";
964
+ const {
965
+ x,
966
+ y
967
+ } = event.detail;
968
+ let anchor = opener ?? target;
969
+ if (!opener && typeof x === "number" && typeof y === "number") {
970
+ // Keep the pointer's place inside the cell, so the menu follows it while the page scrolls.
971
+ const start = target.getBoundingClientRect();
972
+ anchor = {
973
+ getBoundingClientRect: () => {
974
+ const box = target.getBoundingClientRect();
975
+ const left = box.left + x - start.left;
976
+ const top = box.top + y - start.top;
977
+ return {
978
+ left,
979
+ top,
980
+ right: left,
981
+ bottom: top
982
+ };
983
+ }
984
+ };
849
985
  }
986
+ grip = opener;
987
+ setScope(kind);
988
+ focus(event);
989
+ // Focus returns to the menu's cell as it closes; moving a row or column moves that cell.
850
990
  queueMicrotask(() => {
851
- if (disposed || !cell()?.isConnected) return;
852
- const tableTrigger = [...tableTriggers].find(trigger => trigger.isConnected);
853
- const trigger = (window.matchMedia("(max-width: 47.999rem)").matches && tableTrigger?.isConnected ? tableTrigger : undefined) ?? floatingTrigger;
854
- trigger?.click();
991
+ if (!disposed && cell()?.isConnected) openCellMenu?.(anchor, () => cell());
855
992
  });
856
993
  };
857
994
  const mutations = new MutationObserver(schedulePosition);
@@ -870,15 +1007,12 @@ export function TextEditor(props) {
870
1007
  root.addEventListener("mousedown", viewMouseDown);
871
1008
  document.addEventListener("focusin", focusChange);
872
1009
  document.addEventListener("pointerdown", pointer);
1010
+ document.addEventListener("pointerdown", pressBesideMenu, true);
873
1011
  root.addEventListener("k2b-ste-table-structure", structureAnnouncement);
874
1012
  root.addEventListener("k2b-ste-cell-focus", focus);
875
1013
  root.addEventListener("k2b-ste-cell-blur", focusChange);
876
1014
  root.addEventListener("k2b-ste-cell-menu", keyboardMenu);
877
- const tableScroll = event => {
878
- if (menuOpen && event.target instanceof HTMLElement && event.target.classList.contains("k2b-ste-table") && floatingTrigger?.getAttribute("aria-expanded") === "true") floatingTrigger.click();
879
- schedulePosition();
880
- };
881
- window.addEventListener("scroll", tableScroll, true);
1015
+ window.addEventListener("scroll", schedulePosition, true);
882
1016
  window.addEventListener("resize", schedulePosition);
883
1017
  const viewport = window.visualViewport;
884
1018
  const keyboard = () => root.style.setProperty("--k2b-ste-keyboard", `${Math.max(0, window.innerHeight - (viewport?.height ?? window.innerHeight) - (viewport?.offsetTop ?? 0))}px`);
@@ -889,7 +1023,7 @@ export function TextEditor(props) {
889
1023
  updateCheck();
890
1024
  props.onReady?.(editor, {
891
1025
  tableActive: () => mode() === "formatted" && !!cell()?.isConnected,
892
- TableMenu: trigger => menu(false, trigger),
1026
+ TableMenu: trigger => menu(trigger),
893
1027
  formatCell: format => !!editor && !blocked() && !!cell()?.isConnected && formatActiveCell(editor.view, format),
894
1028
  notify: message => setNotice({
895
1029
  message
@@ -903,11 +1037,13 @@ export function TextEditor(props) {
903
1037
  root.removeEventListener("mousedown", viewMouseDown);
904
1038
  document.removeEventListener("focusin", focusChange);
905
1039
  document.removeEventListener("pointerdown", pointer);
1040
+ document.removeEventListener("pointerdown", pressBesideMenu, true);
1041
+ swallow?.abort();
906
1042
  root.removeEventListener("k2b-ste-table-structure", structureAnnouncement);
907
1043
  root.removeEventListener("k2b-ste-cell-focus", focus);
908
1044
  root.removeEventListener("k2b-ste-cell-blur", focusChange);
909
1045
  root.removeEventListener("k2b-ste-cell-menu", keyboardMenu);
910
- window.removeEventListener("scroll", tableScroll, true);
1046
+ window.removeEventListener("scroll", schedulePosition, true);
911
1047
  window.removeEventListener("resize", schedulePosition);
912
1048
  viewport?.removeEventListener("resize", keyboard);
913
1049
  viewport?.removeEventListener("scroll", keyboard);
@@ -945,7 +1081,7 @@ export function TextEditor(props) {
945
1081
  button.disabled = disabled;
946
1082
  });
947
1083
  });
948
- return _$ssr(_tmpl$16, `k2b-text-editor ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-mode", _$escape(mode(), true), false) + _$ssrAttribute("data-readonly", props.readOnly ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", active() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("aria-busy", _$escape(props.loading || !ready(), true), false), _$escape(announcement()), _$escape(_$createComponent(Show, {
1084
+ return _$ssr(_tmpl$15, `k2b-text-editor ${_$escape(props.class ?? "", true)}`, _$ssrAttribute("data-mode", _$escape(mode(), true), false) + _$ssrAttribute("data-readonly", props.readOnly ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("data-active", active() ? "true" : _$escape(undefined, true), false) + _$ssrAttribute("aria-busy", _$escape(props.loading || !ready(), true), false), _$escape(announcement()), _$escape(_$createComponent(Show, {
949
1085
  get when() {
950
1086
  return !props.presentation || "status" in props || check();
951
1087
  },
@@ -1034,7 +1170,7 @@ export function TextEditor(props) {
1034
1170
  });
1035
1171
  },
1036
1172
  get children() {
1037
- return menu(false);
1173
+ return menu();
1038
1174
  }
1039
1175
  })
1040
1176
  });
@@ -1072,22 +1208,15 @@ export function TextEditor(props) {
1072
1208
  get children() {
1073
1209
  return _$ssr(_tmpl$12, _$ssrAttribute("aria-label", _$escape(t().loading, true), false), _$escape(_$createComponent(For, {
1074
1210
  each: [82, 100, 68, 0, 96, 74],
1075
- children: width => _$ssr(_tmpl$17, _$ssrStyleProperty("width:", `${_$escape(width, true)}%`))
1211
+ children: width => _$ssr(_tmpl$16, _$ssrStyleProperty("width:", `${_$escape(width, true)}%`))
1076
1212
  })));
1077
1213
  }
1078
- })), _$ssrAttribute("class", props.presentation ? "k2b-ste-app-host" : "k2b-ste-host", false), _$ssrAttribute("hidden", props.loading || !ready(), true), _$escape(_$createComponent(Show, {
1079
- get when() {
1080
- return _$memo(() => !!(cell() && mode() === "formatted"))() && !menuBlocked();
1081
- },
1082
- get children() {
1083
- return _$ssr(_tmpl$13, _$ssrAttribute("data-readonly", props.readOnly ? "true" : _$escape(undefined, true), false), _$ssrStyle(menuPosition()), _$escape(menu(true)));
1084
- }
1085
- })), _$escape(_$createComponent(Show, {
1214
+ })), _$ssrAttribute("class", props.presentation ? "k2b-ste-app-host" : "k2b-ste-host", false), _$ssrAttribute("hidden", props.loading || !ready(), true), _$ssrStyle(menuPosition()), _$escape(cellMenu()), _$escape(_$createComponent(Show, {
1086
1215
  get when() {
1087
1216
  return notice() || recovery() !== undefined;
1088
1217
  },
1089
1218
  get children() {
1090
- return _$ssr(_tmpl$15, _$escape(_$createComponent(Show, {
1219
+ return _$ssr(_tmpl$14, _$escape(_$createComponent(Show, {
1091
1220
  get when() {
1092
1221
  return notice();
1093
1222
  },
@@ -1097,7 +1226,7 @@ export function TextEditor(props) {
1097
1226
  return _$memo(() => !!notice()?.images)() && props.onAttachImages;
1098
1227
  },
1099
1228
  get fallback() {
1100
- return _$ssr(_tmpl$18, _$escape(notice()?.message));
1229
+ return _$ssr(_tmpl$17, _$escape(notice()?.message));
1101
1230
  },
1102
1231
  get children() {
1103
1232
  return _$createComponent(Button, {
@@ -1144,7 +1273,7 @@ export function TextEditor(props) {
1144
1273
  },
1145
1274
  onClick: () => setNotice(undefined),
1146
1275
  get children() {
1147
- return _$ssr(_tmpl$14);
1276
+ return _$ssr(_tmpl$13);
1148
1277
  }
1149
1278
  });
1150
1279
  }