@threadlabs/looma 0.23.17 → 0.23.18

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.
@@ -403,6 +403,17 @@ export default function controller(host) {
403
403
  const focusItem = (item) => { syncTabStop(item); item.focus(); };
404
404
  const onFocusin = (event) => { if (!interactive(event)) { const item = itemFromEvent(event); if (item) syncTabStop(item); } };
405
405
  const onExpansion = () => requestAnimationFrame(() => syncTabStop());
406
+ let structureFrame = 0;
407
+ const requestStructureSync = () => {
408
+ if (structureFrame) return;
409
+ structureFrame = requestAnimationFrame(() => {
410
+ structureFrame = 0;
411
+ if (element.isConnected) syncStructure();
412
+ });
413
+ };
414
+ const onItemReady = (event) => {
415
+ if (event.target.closest('[role="tree"]') === element) requestStructureSync();
416
+ };
406
417
  const onKeydown = (event) => {
407
418
  if (onMoveKeydown(event)) return;
408
419
  if (interactive(event)) return;
@@ -441,7 +452,7 @@ export default function controller(host) {
441
452
  if (match) { event.preventDefault(); focusItem(match); }
442
453
  }
443
454
  };
444
- const listeners = { dragstart: onDragStart, drag: onDrag, dragenter: onDragOver, dragover: onDragOver, dragleave: onDragLeave, drop: onDrop, dragend: onDragEnd, keydown: onKeydown, focusin: onFocusin, pointerdown: onHandlePointerDown, pointermove: onMovePointer, "ui-tree-expansion-change": onExpansion };
455
+ const listeners = { dragstart: onDragStart, drag: onDrag, dragenter: onDragOver, dragover: onDragOver, dragleave: onDragLeave, drop: onDrop, dragend: onDragEnd, keydown: onKeydown, focusin: onFocusin, pointerdown: onHandlePointerDown, pointermove: onMovePointer, "ui-tree-expansion-change": onExpansion, "ui-tree-item-ready": onItemReady };
445
456
  for (const [name, listener] of Object.entries(listeners)) element.addEventListener(name, listener);
446
457
  element.addEventListener("click", onMoveClick, true);
447
458
  const observer = new MutationObserver((records) => {
@@ -449,6 +460,12 @@ export default function controller(host) {
449
460
  else syncSelection();
450
461
  });
451
462
  observer.observe(element, { childList: true, subtree: true, attributes: true, attributeFilter: ["aria-selected"] });
463
+ // Responsive panels can reveal existing rows without changing the DOM. Keep
464
+ // the previous roving item while hidden, then reconcile the visible layout.
465
+ const size = new ResizeObserver(() => {
466
+ if (element.isConnected && element.getClientRects().length) requestStructureSync();
467
+ });
468
+ size.observe(element);
452
469
  const stop = host.effect(() => {
453
470
  element.setAttribute("aria-label", String(host.props.label.value || "Tree"));
454
471
  syncSelection();
@@ -458,6 +475,8 @@ export default function controller(host) {
458
475
  finish();
459
476
  stop();
460
477
  observer.disconnect();
478
+ size.disconnect();
479
+ cancelAnimationFrame(structureFrame);
461
480
  cancelHover();
462
481
  announcer.remove();
463
482
  for (const [name, listener] of Object.entries(listeners)) element.removeEventListener(name, listener);
@@ -211,6 +211,9 @@ export default function controller(host) {
211
211
  });
212
212
  if (actions) actionsSize.observe(actions);
213
213
 
214
+ // A parent may have sent its initial roving state before this controller loaded.
215
+ element.dispatchEvent(new CustomEvent("ui-tree-item-ready", { bubbles: true }));
216
+
214
217
  return () => {
215
218
  clearTimeout(marqueeRestart);
216
219
  stop?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.23.17",
3
+ "version": "0.23.18",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -1285,6 +1285,12 @@ var LoomaEditor = defineComponent({
1285
1285
  h(Tooltip, { for: removeId }, () => "Remove link")
1286
1286
  ]);
1287
1287
  };
1288
+ const renderLinkContextCard = (instance) => h(Card, {
1289
+ variant: "elevated",
1290
+ padding: linkContextEditing.value ? "sm" : "xs",
1291
+ size: linkContextEditing.value ? "sm" : "content",
1292
+ class: ["looma-editor__link-context", { "looma-editor__link-context--editing": linkContextEditing.value }]
1293
+ }, () => [renderLinkContext(instance)]);
1288
1294
  const captureBlockAction = () => {
1289
1295
  const instance = editor.value;
1290
1296
  const head = instance?.state.selection.$head;
@@ -1993,16 +1999,26 @@ var LoomaEditor = defineComponent({
1993
1999
  instance && props.editable && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" || props.toolbarMode === "contextual") ? h(BubbleMenu, {
1994
2000
  editor: instance,
1995
2001
  pluginKey: "looma-text-formatting-menu",
1996
- shouldShow: ({ editor: menuEditor, from, to }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && (props.toolbarMode === "contextual" ? menuEditor.isFocused : shouldShowTextFormattingToolbar(menuEditor, from, to)),
2002
+ shouldShow: ({ editor: menuEditor, from, to }) => (props.toolbarMode !== "popover" || !props.toolbarOpen) && (props.toolbarMode === "contextual" ? menuEditor.isFocused || linkContextEditing.value : shouldShowTextFormattingToolbar(menuEditor, from, to)),
1997
2003
  tippyOptions: {
1998
2004
  appendTo: () => root.value?.closest("dialog[open], [popover]") ?? document.body,
1999
2005
  onShow: () => announceOverlayOpen(root.value?.ownerDocument ?? document, root.value),
2000
2006
  duration: 100,
2001
2007
  maxWidth: "none",
2002
- placement: "top"
2008
+ placement: "top",
2009
+ ...props.toolbarMode === "contextual" && root.value ? { popperOptions: { modifiers: [{
2010
+ name: "flip",
2011
+ options: { boundary: root.value }
2012
+ }, {
2013
+ name: "preventOverflow",
2014
+ options: {
2015
+ boundary: root.value,
2016
+ altAxis: true
2017
+ }
2018
+ }] } } : {}
2003
2019
  }
2004
- }, { default: () => renderToolbar(instance, true, props.toolbarMode === "popover") }) : null,
2005
- instance && props.editable && !mobile.value ? h(BubbleMenu, {
2020
+ }, { default: () => props.toolbarMode === "contextual" ? h(Stack, { gap: "xs" }, () => [renderToolbar(instance, true), (linkContextEditing.value || instance.state.selection.empty && instance.isActive("link")) && !linkOpen.value ? renderLinkContextCard(instance) : null]) : renderToolbar(instance, true, props.toolbarMode === "popover") }) : null,
2021
+ instance && props.editable && !mobile.value && props.toolbarMode !== "contextual" ? h(BubbleMenu, {
2006
2022
  editor: instance,
2007
2023
  pluginKey: "looma-link-context-menu",
2008
2024
  shouldShow: ({ editor: menuEditor, from, to }) => from === to && menuEditor.isActive("link") && !linkOpen.value,
@@ -2013,12 +2029,7 @@ var LoomaEditor = defineComponent({
2013
2029
  maxWidth: Math.min(480, window.innerWidth - 24),
2014
2030
  placement: "bottom"
2015
2031
  }
2016
- }, { default: () => h(Card, {
2017
- variant: "elevated",
2018
- padding: linkContextEditing.value ? "sm" : "xs",
2019
- size: linkContextEditing.value ? "sm" : "content",
2020
- class: ["looma-editor__link-context", { "looma-editor__link-context--editing": linkContextEditing.value }]
2021
- }, () => [renderLinkContext(instance)]) }) : null,
2032
+ }, { default: () => renderLinkContextCard(instance) }) : null,
2022
2033
  instance && props.editable && !mobile.value && props.toolbarMode === "popover" && props.toolbarTriggerId ? h(Popover, {
2023
2034
  class: "looma-editor__formatting-popover",
2024
2035
  role: "region",