@threadlabs/looma 0.31.1 → 0.31.2

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.
@@ -0,0 +1,7 @@
1
+ /** Resolve the inherited pointer-intent delay; CSS time values and millisecond numbers are accepted. */
2
+ export function tooltipShowDelay(element, fallback = 500) {
3
+ if (!element) return fallback;
4
+ const value = element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("--ui-tooltip-show-delay").trim();
5
+ const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s)?$/.exec(value);
6
+ return match ? Number(match[1]) * (match[2] === "s" ? 1000 : 1) : fallback;
7
+ }
@@ -1,3 +1,4 @@
1
+ import { tooltipShowDelay } from "../shared/tooltip.js";
1
2
  import { closeOverlay, createAnchoredSurface, createIdResolver, onOverlayOpen, openOverlay } from "../shared/overlay.js";
2
3
 
3
4
  const warmTooltips = new WeakMap();
@@ -61,7 +62,7 @@ export function connect(host) {
61
62
  showTimer = setTimeout(() => {
62
63
  showTimer = null;
63
64
  setOpen(true, "pointer");
64
- }, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, Number(host.props.showDelay.value ?? 500)));
65
+ }, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, host.props.showDelay.inputValue != null ? Number(host.props.showDelay.value) : tooltipShowDelay(element)));
65
66
  };
66
67
  const onLeave = () => {
67
68
  clearShow();
@@ -134,6 +135,8 @@ export function connect(host) {
134
135
  if (how === "hover") {
135
136
  trigger.addEventListener("pointerenter", onEnter);
136
137
  trigger.addEventListener("pointerleave", onLeave);
138
+ // Editor decorations can acquire their tooltip after the pointer has already entered.
139
+ if (trigger.matches(":hover")) onEnter({ pointerType: "mouse" });
137
140
  }
138
141
  trigger.addEventListener("focusin", onFocusin);
139
142
  trigger.addEventListener("focusout", onFocusout);
@@ -11155,6 +11155,26 @@ var TableHeader = Node$1.create({
11155
11155
  });
11156
11156
  //#endregion
11157
11157
  //#region src/editor/extensions/table-formatting.ts
11158
+ /** Table chrome lives outside the horizontal scroller, within the table's document block. */
11159
+ var LoomaTableView = class extends TableView {
11160
+ toolbarHost;
11161
+ constructor(node, cellMinWidth) {
11162
+ super(node, cellMinWidth);
11163
+ const scroller = this.dom;
11164
+ this.dom = document.createElement("div");
11165
+ this.dom.dataset.loomaTableBlock = "";
11166
+ this.toolbarHost = document.createElement("div");
11167
+ this.toolbarHost.dataset.loomaTableToolbarHost = "";
11168
+ this.toolbarHost.contentEditable = "false";
11169
+ this.dom.append(scroller, this.toolbarHost);
11170
+ }
11171
+ ignoreMutation(mutation) {
11172
+ return mutation.type !== "selection" && this.toolbarHost.contains(mutation.target) || super.ignoreMutation(mutation);
11173
+ }
11174
+ stopEvent(event) {
11175
+ return event.target instanceof globalThis.Node && this.toolbarHost.contains(event.target);
11176
+ }
11177
+ };
11158
11178
  /**
11159
11179
  * Looma's table behavior policy: resizable columns, stable minimum cell width,
11160
11180
  * and no independently resizable trailing column. The narrow handle is also a
@@ -11165,6 +11185,7 @@ var TableHeader = Node$1.create({
11165
11185
  */
11166
11186
  var LoomaTable = Table.configure({
11167
11187
  resizable: true,
11188
+ View: LoomaTableView,
11168
11189
  handleWidth: 3,
11169
11190
  cellMinWidth: 112,
11170
11191
  lastColumnResizable: false
@@ -17,17 +17,8 @@ export declare const LOOMA_TABLE_CONTROLS_CLOSED: LoomaTableControls;
17
17
  * with nothing selected; narrow screens use the editing dock instead of the toolbar and overlay.
18
18
  */
19
19
  export declare function tableControls(state: ActiveTableUiState, selectionInTable: boolean, narrow: boolean): LoomaTableControls;
20
- /**
21
- * Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
22
- * above the table when there is room and below it otherwise.
23
- */
24
- export declare function tableToolbarPlacement(table: DOMRect, viewportWidth: number): {
25
- top: string;
26
- left: string;
27
- maxWidth: string;
28
- };
29
- /** The overlay covers the table exactly. */
30
- export declare const tableOverlayPlacement: (table: DOMRect) => {
20
+ /** Inline controls cover the table in the editor's coordinate system. */
21
+ export declare const tableOverlayPlacement: (table: DOMRect, editor: DOMRect) => {
31
22
  top: string;
32
23
  left: string;
33
24
  width: string;
@@ -22,4 +22,4 @@ export interface LoomaToolbarTooltip {
22
22
  destroy(): void;
23
23
  }
24
24
  /** Calls `onChange` after every state change. */
25
- export declare function createToolbarTooltip(onChange: (state: LoomaToolbarTooltipState) => void): LoomaToolbarTooltip;
25
+ export declare function createToolbarTooltip(onChange: (state: LoomaToolbarTooltipState) => void, delay?: () => number): LoomaToolbarTooltip;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.31.1",
3
+ "version": "0.31.2",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -10438,6 +10438,30 @@ html[data-ui-input-modality="touch"]
10438
10438
  :is([data-component~="ui-editor-surface"], .ui-editor-surface) .ProseMirror[data-link-navigation] a[href] {
10439
10439
  cursor: pointer;
10440
10440
  }
10441
+
10442
+ /* Inline table affordances share the document's scroll chain; popups use the top layer. */
10443
+ .looma-editor__table-overlay-shell {
10444
+ position: absolute;
10445
+ z-index: var(--ui-editor-overlay-z-index, 299);
10446
+ overflow: visible;
10447
+ pointer-events: none;
10448
+ }
10449
+
10450
+ /* The toolbar follows its table, lifting inside the document scroller only when needed. */
10451
+ [data-looma-table-toolbar-host] { display: contents; }
10452
+ .looma-editor__table-toolbar-shell {
10453
+ position: sticky;
10454
+ bottom: var(--ui-space-3);
10455
+ z-index: var(--ui-editor-popover-z-index, 300);
10456
+ width: max-content;
10457
+ max-width: 100%;
10458
+ margin: var(--ui-space-3) auto 0;
10459
+ overflow-x: auto;
10460
+ }
10461
+
10462
+ .looma-editor .ProseMirror [data-looma-table-block] + p {
10463
+ margin-top: var(--ui-editor-table-following-gap, var(--ui-space-4));
10464
+ }
10441
10465
  /**
10442
10466
  * Looma editor styles (tokens-based).
10443
10467
  * Tables, context menu, insert-table grid, slash menu. Use with ProseMirror/Tiptap content.
@@ -10738,7 +10762,6 @@ html[data-ui-input-modality="touch"]
10738
10762
 
10739
10763
  .looma-editor__table-picker-popover,
10740
10764
  .looma-editor__chip-popover,
10741
- .looma-editor__table-toolbar-shell,
10742
10765
  .looma-editor__table-menu-shell {
10743
10766
  z-index: var(--ui-editor-popover-z-index, 300);
10744
10767
  }
@@ -10768,9 +10791,7 @@ html[data-ui-input-modality="touch"]
10768
10791
  outline-offset: var(--ui-focus-width);
10769
10792
  }
10770
10793
 
10771
- .looma-editor__table-toolbar-shell,
10772
- .looma-editor__table-menu-shell,
10773
- .looma-editor__table-overlay-shell {
10794
+ .looma-editor__table-menu-shell {
10774
10795
  position: fixed;
10775
10796
  }
10776
10797
 
@@ -10836,12 +10857,6 @@ html[data-ui-input-modality="touch"]
10836
10857
  color: var(--ui-text-primary, #111827);
10837
10858
  }
10838
10859
 
10839
- .looma-editor__table-overlay-shell {
10840
- z-index: var(--ui-editor-overlay-z-index, 299);
10841
- overflow: visible;
10842
- pointer-events: none;
10843
- }
10844
-
10845
10860
  .looma-editor .ProseMirror .looma-mention {
10846
10861
  padding: 0.08em 0.28em;
10847
10862
  color: var(--ui-editor-mention-color, var(--ui-accent-active, var(--ui-accent-solid)));
@@ -1,4 +1,4 @@
1
- import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, shallowRef, useId, watch } from "vue";
1
+ import { Teleport, computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, shallowRef, useId, watch } from "vue";
2
2
  import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getTableOfContentsEntries, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeTableOfContentsConfig } from "@threadlabs/looma/editor";
3
3
  import { Button, Card, EditorBlockActions, EditorChipEditor, EditorCodeLanguage, EditorFootnoteEditor, EditorImageToolbar, EditorInsertTableGrid, EditorLinkCard, EditorLinkForm, EditorMentionMenu, EditorMentionMenuItem, EditorSlashMenu, EditorSlashMenuGroup, EditorSlashMenuItem, EditorSurface, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Popover, Stack, StatusMessage, Text, Tooltip } from "@threadlabs/looma/vue";
4
4
  import { Editor, Extension, InputRule, Node as Node$1, PasteRule, getText, getTextSerializersFromSchema, isNodeSelection, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
@@ -2291,25 +2291,10 @@ function tableControls(state, selectionInTable, narrow) {
2291
2291
  capabilities: state.capabilities
2292
2292
  };
2293
2293
  }
2294
- /** Space, in pixels, the toolbar needs above a table; with less it sits below. */
2295
- var ROOM_ABOVE$1 = 76;
2296
- /**
2297
- * Centers the table toolbar on the table, at most 420px wide and 12px inside the viewport's sides,
2298
- * above the table when there is room and below it otherwise.
2299
- */
2300
- function tableToolbarPlacement(table, viewportWidth) {
2301
- const width = Math.min(420, viewportWidth - 24);
2302
- const left = Math.min(Math.max(12, table.left + (table.width - width) / 2), Math.max(12, viewportWidth - width - 12));
2303
- return {
2304
- top: `${table.top > ROOM_ABOVE$1 ? table.top - 52 : table.bottom + 12}px`,
2305
- left: `${left}px`,
2306
- maxWidth: `${width}px`
2307
- };
2308
- }
2309
- /** The overlay covers the table exactly. */
2310
- var tableOverlayPlacement = (table) => ({
2311
- top: `${table.top}px`,
2312
- left: `${table.left}px`,
2294
+ /** Inline controls cover the table in the editor's coordinate system. */
2295
+ var tableOverlayPlacement = (table, editor) => ({
2296
+ top: `${table.top - editor.top}px`,
2297
+ left: `${table.left - editor.left}px`,
2313
2298
  width: `${table.width}px`,
2314
2299
  height: `${table.height}px`
2315
2300
  });
@@ -3114,8 +3099,17 @@ function formatEditorShortcut(keys, apple) {
3114
3099
  ].filter(Boolean).join("+");
3115
3100
  }
3116
3101
  var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
3102
+ //#endregion
3103
+ //#region src/components/shared/tooltip.js
3104
+ /** Resolve the inherited pointer-intent delay; CSS time values and millisecond numbers are accepted. */
3105
+ function tooltipShowDelay$1(element, fallback = 500) {
3106
+ if (!element) return fallback;
3107
+ const value = element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("--ui-tooltip-show-delay").trim();
3108
+ const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s)?$/.exec(value);
3109
+ return match ? Number(match[1]) * (match[2] === "s" ? 1e3 : 1) : fallback;
3110
+ }
3117
3111
  /** Calls `onChange` after every state change. */
3118
- function createToolbarTooltip(onChange) {
3112
+ function createToolbarTooltip(onChange, delay = () => 400) {
3119
3113
  let state = {
3120
3114
  for: "",
3121
3115
  label: "",
@@ -3150,7 +3144,7 @@ function createToolbarTooltip(onChange) {
3150
3144
  if (!open) timer = setTimeout(() => {
3151
3145
  timer = void 0;
3152
3146
  set({ open: true });
3153
- }, 400);
3147
+ }, delay());
3154
3148
  },
3155
3149
  hide() {
3156
3150
  clear();
@@ -10530,6 +10524,15 @@ defineComponent$1("ui-text", createUiText, [
10530
10524
  "wrap"
10531
10525
  ]);
10532
10526
  //#endregion
10527
+ //#region components/shared/tooltip.js
10528
+ /** Resolve the inherited pointer-intent delay; CSS time values and millisecond numbers are accepted. */
10529
+ function tooltipShowDelay(element, fallback = 500) {
10530
+ if (!element) return fallback;
10531
+ const value = element.ownerDocument.defaultView.getComputedStyle(element).getPropertyValue("--ui-tooltip-show-delay").trim();
10532
+ const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s)?$/.exec(value);
10533
+ return match ? Number(match[1]) * (match[2] === "s" ? 1e3 : 1) : fallback;
10534
+ }
10535
+ //#endregion
10533
10536
  //#region components/ui-tooltip/ui-tooltip.js
10534
10537
  var ui_tooltip_exports = /* @__PURE__ */ __exportAll({ connect: () => connect$4 });
10535
10538
  var warmTooltips = /* @__PURE__ */ new WeakMap();
@@ -10606,7 +10609,7 @@ function connect$4(host) {
10606
10609
  showTimer = setTimeout(() => {
10607
10610
  showTimer = null;
10608
10611
  setOpen(true, "pointer");
10609
- }, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, Number(host.props.showDelay.value ?? 500)));
10612
+ }, warm.active > 0 || warm.until > Date.now() ? 0 : Math.max(0, host.props.showDelay.inputValue != null ? Number(host.props.showDelay.value) : tooltipShowDelay(element)));
10610
10613
  };
10611
10614
  const onLeave = () => {
10612
10615
  clearShow();
@@ -10676,6 +10679,7 @@ function connect$4(host) {
10676
10679
  if (how === "hover") {
10677
10680
  trigger.addEventListener("pointerenter", onEnter);
10678
10681
  trigger.addEventListener("pointerleave", onLeave);
10682
+ if (trigger.matches(":hover")) onEnter({ pointerType: "mouse" });
10679
10683
  }
10680
10684
  trigger.addEventListener("focusin", onFocusin);
10681
10685
  trigger.addEventListener("focusout", onFocusout);
@@ -14758,7 +14762,7 @@ var LoomaEditor = defineComponent({
14758
14762
  const toolbarScope = `looma-editor-tool-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
14759
14763
  const toolbarTooltip = createToolbarTooltip((state) => {
14760
14764
  tooltip.value = state;
14761
- });
14765
+ }, () => tooltipShowDelay$1(root.value));
14762
14766
  const tooltip = shallowRef(toolbarTooltip.state);
14763
14767
  const resizeHintFor = ref("");
14764
14768
  let resizeHintSequence = 0;
@@ -14830,7 +14834,7 @@ var LoomaEditor = defineComponent({
14830
14834
  rows: 0,
14831
14835
  cols: 0,
14832
14836
  geometry: null,
14833
- toolbarStyle: {},
14837
+ toolbarTarget: null,
14834
14838
  overlayStyle: {},
14835
14839
  menuStyle: {},
14836
14840
  capabilities: LOOMA_TABLE_CONTROLS_CLOSED.capabilities
@@ -15362,8 +15366,8 @@ var LoomaEditor = defineComponent({
15362
15366
  tableUi.rows = Math.max(0, geometry.rowBoundaries.length - 1);
15363
15367
  tableUi.cols = Math.max(0, geometry.columnBoundaries.length - 1);
15364
15368
  tableUi.geometry = geometry;
15365
- tableUi.toolbarStyle = tableToolbarPlacement(rect, window.innerWidth);
15366
- tableUi.overlayStyle = tableOverlayPlacement(rect);
15369
+ tableUi.toolbarTarget = table.closest("[data-looma-table-block]")?.querySelector("[data-looma-table-toolbar-host]") ?? null;
15370
+ tableUi.overlayStyle = tableOverlayPlacement(rect, root.value.getBoundingClientRect());
15367
15371
  };
15368
15372
  const onEditorFocus = () => {
15369
15373
  editorFocused.value = true;
@@ -15429,13 +15433,10 @@ var LoomaEditor = defineComponent({
15429
15433
  presentation?.show();
15430
15434
  };
15431
15435
  };
15432
- const tableToolbarRef = popupRef(tableToolbarShell);
15433
15436
  const codeLanguageRef = popupRef(codeLanguageShell, true);
15434
15437
  const mobileToolbarRef = popupRef(mobileToolbarShell, true);
15435
- const presentTableOverlay = popupRef(tableOverlayShell);
15436
15438
  const tableOverlayRef = (node) => {
15437
- presentTableOverlay(node);
15438
- if (node instanceof HTMLElement) node.hidden = !tableUi.overlayOpen;
15439
+ tableOverlayShell.value = node instanceof HTMLElement ? node : null;
15439
15440
  };
15440
15441
  const onDocumentPointerDown = (event) => {
15441
15442
  const path = typeof event.composedPath === "function" ? event.composedPath() : [];
@@ -16048,8 +16049,6 @@ var LoomaEditor = defineComponent({
16048
16049
  }, [
16049
16050
  resizeHintFor.value ? h(Tooltip, {
16050
16051
  for: resizeHintFor.value,
16051
- open: true,
16052
- trigger: "focus",
16053
16052
  placement: "top",
16054
16053
  inverse: true
16055
16054
  }, () => "Drag to resize column") : null,
@@ -16263,12 +16262,11 @@ var LoomaEditor = defineComponent({
16263
16262
  mention.select?.(event.detail.index);
16264
16263
  }
16265
16264
  }) : null,
16266
- tableUi.toolbarOpen ? h("div", {
16265
+ tableUi.toolbarOpen && tableUi.toolbarTarget ? h(Teleport, { to: tableUi.toolbarTarget }, [h("div", {
16267
16266
  key: "looma-table-toolbar-shell",
16268
- ref: tableToolbarRef,
16269
- class: "looma-editor__table-toolbar-shell",
16270
- style: tableUi.toolbarStyle
16271
- }, [h(EditorTableToolbar, tableProps)]) : null,
16267
+ ref: tableToolbarShell,
16268
+ class: "looma-editor__table-toolbar-shell"
16269
+ }, [h(EditorTableToolbar, tableProps)])]) : null,
16272
16270
  instance && props.editable && !mobile.value ? h("div", {
16273
16271
  key: "looma-table-overlay-shell",
16274
16272
  ref: tableOverlayRef,