@threadlabs/looma 0.26.25 → 0.27.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 (80) hide show
  1. package/components/shared/editor.js +4 -5
  2. package/components/shared/overlay.js +231 -55
  3. package/components/ui-affordance-scope/ui-affordance-scope.js +4 -8
  4. package/components/ui-card/ui-card.html +3 -0
  5. package/components/ui-dialog/ui-dialog.html +7 -5
  6. package/components/ui-dialog/ui-dialog.js +67 -7
  7. package/components/ui-editor-mention-menu/ui-editor-mention-menu.html +6 -0
  8. package/components/ui-editor-mention-menu/ui-editor-mention-menu.js +5 -9
  9. package/components/ui-editor-slash-menu/ui-editor-slash-menu.html +6 -0
  10. package/components/ui-editor-slash-menu/ui-editor-slash-menu.js +5 -9
  11. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.html +21 -4
  12. package/components/ui-editor-table-context-menu/ui-editor-table-context-menu.js +50 -22
  13. package/components/ui-editor-table-overlay/ui-editor-table-overlay.js +4 -6
  14. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.html +3 -11
  15. package/components/ui-editor-table-toolbar/ui-editor-table-toolbar.js +33 -44
  16. package/components/ui-icon-button/ui-icon-button.html +12 -2
  17. package/components/ui-listbox/ui-listbox.html +22 -17
  18. package/components/ui-menu/ui-menu.js +5 -3
  19. package/components/ui-popover/ui-popover.js +3 -2
  20. package/components/ui-scroll-area/ui-scroll-area.html +16 -0
  21. package/components/ui-search-shell/ui-search-shell.html +1 -1
  22. package/components/ui-search-shell/ui-search-shell.js +3 -5
  23. package/components/ui-section/ui-section.html +3 -0
  24. package/components/ui-toast/ui-toast.html +2 -1
  25. package/components/ui-toast/ui-toast.js +18 -2
  26. package/components/ui-toast-region/ui-toast-region.js +11 -3
  27. package/components/ui-tooltip/ui-tooltip.html +2 -1
  28. package/components/ui-tooltip/ui-tooltip.js +20 -0
  29. package/dist/index.js +1 -1
  30. package/editor/chunks/extensions.js +2 -0
  31. package/editor/extensions/slash-command.d.ts +2 -0
  32. package/editor/mention-contract.d.ts +2 -0
  33. package/package.json +1 -1
  34. package/styles/ui-card.css +3 -0
  35. package/styles/ui-dialog.css +4 -2
  36. package/styles/ui-editor-mention-menu.css +7 -1
  37. package/styles/ui-editor-slash-menu.css +7 -1
  38. package/styles/ui-editor-table-context-menu.css +29 -5
  39. package/styles/ui-editor-table-toolbar.css +3 -11
  40. package/styles/ui-icon-button.css +9 -2
  41. package/styles/ui-listbox.css +6 -0
  42. package/styles/ui-scroll-area.css +13 -0
  43. package/styles/ui-search-shell.css +1 -1
  44. package/styles/ui-section.css +3 -0
  45. package/vanilla/UiDialog.js +10 -6
  46. package/vanilla/UiIconButton.d.ts +1 -0
  47. package/vanilla/UiIconButton.js +14 -14
  48. package/vanilla/UiListbox.js +10 -9
  49. package/vanilla/UiScrollArea.d.ts +1 -0
  50. package/vanilla/UiScrollArea.js +4 -4
  51. package/vue/UiCard.vue +3 -0
  52. package/vue/UiDialog.vue +12 -6
  53. package/vue/UiEditorMentionMenu.vue +6 -0
  54. package/vue/UiEditorSlashMenu.vue +6 -0
  55. package/vue/UiEditorTableContextMenu.vue +21 -4
  56. package/vue/UiEditorTableToolbar.vue +3 -11
  57. package/vue/UiIconButton.d.ts +9 -0
  58. package/vue/UiIconButton.vue +31 -2
  59. package/vue/UiListbox.vue +20 -15
  60. package/vue/UiScrollArea.d.ts +9 -0
  61. package/vue/UiScrollArea.vue +39 -1
  62. package/vue/UiSearchShell.vue +1 -1
  63. package/vue/UiSection.vue +3 -0
  64. package/vue/UiToast.vue +1 -1
  65. package/vue/UiTooltip.vue +1 -1
  66. package/vue/chunks/UiCard.js +1 -1
  67. package/vue/chunks/UiDialog.js +25 -8
  68. package/vue/chunks/UiEditorMentionMenu.js +1 -1
  69. package/vue/chunks/UiEditorSlashMenu.js +1 -1
  70. package/vue/chunks/UiEditorTableContextMenu.js +1 -1
  71. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  72. package/vue/chunks/UiIconButton.js +26 -1
  73. package/vue/chunks/UiListbox.js +12 -12
  74. package/vue/chunks/UiScrollArea.js +33 -2
  75. package/vue/chunks/UiSearchShell.js +1 -1
  76. package/vue/chunks/UiSection.js +1 -1
  77. package/vue/chunks/UiToast.js +2 -2
  78. package/vue/chunks/UiTooltip.js +2 -2
  79. package/vue/components.css +340 -289
  80. package/vue/editor/index.js +225 -40
@@ -4,7 +4,7 @@ function inputFor(event) {
4
4
  return event.detail === 0 ? "keyboard" : "pointer";
5
5
  }
6
6
 
7
- /** Authored toasts request dismissal; their consumer owns removal from the rendered list. */
7
+ /** An authored toast hides itself when dismissed, like a dialog closing; clearing hidden shows it again. */
8
8
  function connect(host) {
9
9
  const element = host.element;
10
10
  const { action } = host.refs;
@@ -19,11 +19,13 @@ function connect(host) {
19
19
  if (dismissed) return;
20
20
  dismissed = true;
21
21
  clearTimeout(timer);
22
+ element.hidden = true;
22
23
  host.dispatch("dismiss", { id: element.id, reason, trigger });
23
24
  };
24
25
  const start = () => {
25
26
  clearTimeout(timer);
26
- if (paused || dismissed || remaining <= 0) return;
27
+ // The duration counts only while the toast is shown; a hidden toast has nothing to time out.
28
+ if (paused || dismissed || element.hidden || remaining <= 0) return;
27
29
  started = Date.now();
28
30
  timer = setTimeout(() => dismiss("timeout", "programmatic"), remaining);
29
31
  };
@@ -50,6 +52,19 @@ function connect(host) {
50
52
  remaining = duration;
51
53
  start();
52
54
  });
55
+ // Hidden by the app, the timer stops; shown (again), it is a fresh request with the full duration.
56
+ const shown = new MutationObserver(() => {
57
+ if (element.hidden) {
58
+ clearTimeout(timer);
59
+ timer = null;
60
+ return;
61
+ }
62
+ dismissed = false;
63
+ paused = false;
64
+ remaining = Math.max(0, Number(host.props.duration.value) || 0);
65
+ start();
66
+ });
67
+ shown.observe(element, { attributes: true, attributeFilter: ["hidden"] });
53
68
  element.addEventListener("click", onClick);
54
69
  element.addEventListener("pointerenter", pause);
55
70
  element.addEventListener("pointerleave", resume);
@@ -58,6 +73,7 @@ function connect(host) {
58
73
  start();
59
74
  return () => {
60
75
  stop();
76
+ shown.disconnect();
61
77
  clearTimeout(timer);
62
78
  element.removeEventListener("click", onClick);
63
79
  element.removeEventListener("pointerenter", pause);
@@ -5,13 +5,13 @@ let toastIds = 0;
5
5
 
6
6
  /**
7
7
  * Shows the region while it has authored or generated messages. Generated messages dismiss by
8
- * action or timeout; authored ui-toast children own their removal through the dismiss event.
8
+ * action or timeout; authored ui-toast children hide themselves, and a hidden one does not count.
9
9
  */
10
10
  function connect(host) {
11
11
  const element = host.element;
12
12
  const [trigger, stopTracking] = trackTrigger(host);
13
13
  const toasts = () => host.state.toasts ?? [];
14
- const authored = () => Array.from(element.children).some((child) => !child.classList.contains("toast"));
14
+ const authored = () => Array.from(element.children).some((child) => !child.classList.contains("toast") && !child.hidden);
15
15
  let announcedVisible = false;
16
16
  const sync = () => {
17
17
  const visible = authored() || toasts().length > 0;
@@ -86,8 +86,16 @@ function connect(host) {
86
86
  if (event.target === element && typeof event.detail?.message === "string") add(event.detail.message, event.detail);
87
87
  };
88
88
  const stop = host.effect(sync);
89
- const observer = new MutationObserver(sync);
89
+ // Children come and go, and an authored toast hides itself: watch each child's hidden, not the whole subtree.
90
+ const watchChildren = () => {
91
+ for (const child of element.children) observer.observe(child, { attributeFilter: ["hidden"] });
92
+ };
93
+ const observer = new MutationObserver(() => {
94
+ watchChildren();
95
+ sync();
96
+ });
90
97
  observer.observe(element, { childList: true });
98
+ watchChildren();
91
99
  element.addEventListener("click", onClick);
92
100
  element.addEventListener("command", onCommand);
93
101
  element.addEventListener("show-toast", onShowToast);
@@ -10,7 +10,8 @@
10
10
  >Positions the description around its trigger. Start and end on top or bottom follow the writing direction.</prop>
11
11
  <prop name="trigger" type="keyword" values="hover, click, focus" default="hover"
12
12
  >How the trigger opens it. hover also opens on keyboard focus; click toggles it and suits a help button, where hovering a question mark says nothing; focus
13
- opens only while the trigger holds focus.</prop>
13
+ opens only on keyboard-visible focus. Action activation dismisses hover/focus hints without removing focus; a fresh hover or keyboard focus can open them
14
+ again.</prop>
14
15
  <prop name="showDelay" type="number" default="500">Waits this many milliseconds before showing after pointer entry.</prop>
15
16
  <prop name="hideDelay" type="number" default="100">Waits this many milliseconds before hiding after pointer exit.</prop>
16
17
  <prop name="inverse" type="boolean" default="false">Use the high-contrast inverse surface (dark on light themes, light on dark themes).</prop>
@@ -28,6 +28,7 @@ function connect(host) {
28
28
  const overlayId = element.id || `ui-tooltip-${Math.random().toString(36).slice(2, 11)}`;
29
29
  if (!element.id) element.id = overlayId;
30
30
  let focused = false;
31
+ let pointerFocused = false;
31
32
  let trigger = null;
32
33
  let surface = null;
33
34
  let showTimer = null;
@@ -71,6 +72,8 @@ function connect(host) {
71
72
  }, Math.max(0, Number(host.props.hideDelay.value ?? 100)));
72
73
  };
73
74
  const onFocusin = () => {
75
+ // A pointer click may focus the action, but only visible keyboard focus keeps its hint open.
76
+ if (pointerFocused || !trigger?.matches(":focus-visible")) return;
74
77
  focused = true;
75
78
  clearTimers();
76
79
  setOpen(true, "keyboard");
@@ -78,6 +81,7 @@ function connect(host) {
78
81
  const onFocusout = (event) => {
79
82
  if (event.relatedTarget && element.contains(event.relatedTarget)) return;
80
83
  focused = false;
84
+ pointerFocused = false;
81
85
  clearTimers();
82
86
  setOpen(false, "keyboard");
83
87
  };
@@ -88,6 +92,8 @@ function connect(host) {
88
92
  else trigger.removeAttribute("aria-describedby");
89
93
  trigger.removeEventListener("keydown", onKeydown);
90
94
  trigger.removeEventListener("click", onClick);
95
+ trigger.removeEventListener("click", onActivate);
96
+ trigger.removeEventListener("pointerdown", onPointerDown);
91
97
  trigger.removeAttribute("aria-expanded");
92
98
  trigger.removeEventListener("pointerenter", onEnter);
93
99
  trigger.removeEventListener("pointerleave", onLeave);
@@ -98,6 +104,18 @@ function connect(host) {
98
104
  clearTimers();
99
105
  setOpen(!host.state.internalOpen, "pointer");
100
106
  };
107
+ const onPointerDown = () => {
108
+ pointerFocused = true;
109
+ focused = false;
110
+ clearTimers();
111
+ setOpen(false, "pointer");
112
+ };
113
+ const onActivate = (event) => {
114
+ // Action hints are spent once the action runs; the next hover or keyboard focus opens anew.
115
+ clearTimers();
116
+ focused = false;
117
+ setOpen(false, event.detail === 0 ? "keyboard" : "pointer");
118
+ };
101
119
  const attach = () => {
102
120
  if (!trigger) return;
103
121
  const ids = new Set((trigger.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean));
@@ -111,6 +129,8 @@ function connect(host) {
111
129
  trigger.setAttribute("aria-expanded", String(Boolean(host.state.internalOpen)));
112
130
  return;
113
131
  }
132
+ trigger.addEventListener("pointerdown", onPointerDown);
133
+ trigger.addEventListener("click", onActivate);
114
134
  if (how === "hover") {
115
135
  trigger.addEventListener("pointerenter", onEnter);
116
136
  trigger.addEventListener("pointerleave", onLeave);