@threadlabs/looma 0.15.7 → 0.16.1

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.
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, useId, watch } from "vue";
2
- import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, resolveTableCellAt, shouldShowTextFormattingToolbar } from "@threadlabs/looma/editor";
2
+ import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, resolveTableCellAt, shouldShowTextFormattingToolbar } from "@threadlabs/looma/editor";
3
3
  import { EditorInsertTableGrid, EditorMentionMenu, EditorSlashMenu, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Menu, MenuItem, Popover, Tooltip } from "@threadlabs/looma/vue";
4
4
  import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
5
5
  import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
@@ -353,6 +353,11 @@ var LoomaEditor = defineComponent({
353
353
  const linkText = ref("");
354
354
  const linkNewTab = ref(true);
355
355
  const linkError = ref("");
356
+ const chipAnchorId = `looma-editor-chip-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
357
+ const chipInput = ref(null);
358
+ const chipOpen = ref(false);
359
+ const chipLabel = ref("");
360
+ const chipColor = ref("neutral");
356
361
  let linkSelection = null;
357
362
  const toolbarScope = `looma-editor-tool-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
358
363
  const tooltipFor = ref("");
@@ -435,8 +440,43 @@ var LoomaEditor = defineComponent({
435
440
  menuStyle: {},
436
441
  capabilities: { ...EMPTY_CAPABILITIES }
437
442
  });
443
+ const openChipEditor = (instance, position) => {
444
+ if (!props.editable) return;
445
+ const node = instance.state.doc.nodeAt(position);
446
+ const element = instance.view.nodeDOM(position);
447
+ if (node?.type.name !== "loomaChip" || !(element instanceof HTMLElement)) return;
448
+ root.value?.querySelector(`#${chipAnchorId}`)?.removeAttribute("id");
449
+ element.id = chipAnchorId;
450
+ chipLabel.value = String(node.attrs.label ?? "");
451
+ chipColor.value = normalizeLoomaChipColor(node.attrs.color);
452
+ chipOpen.value = true;
453
+ nextTick(() => chipInput.value?.focus());
454
+ };
455
+ const changeChip = (changes) => {
456
+ const instance = editor.value;
457
+ const element = root.value?.querySelector(`#${chipAnchorId}`);
458
+ if (!instance || !element) {
459
+ chipOpen.value = false;
460
+ return;
461
+ }
462
+ const position = instance.view.posAtDOM(element, 0);
463
+ const node = instance.state.doc.nodeAt(position);
464
+ if (node?.type.name !== "loomaChip") {
465
+ chipOpen.value = false;
466
+ return;
467
+ }
468
+ instance.view.dispatch(instance.state.tr.setNodeMarkup(position, void 0, {
469
+ ...node.attrs,
470
+ ...changes
471
+ }));
472
+ };
473
+ const closeChip = (focusChip = false) => {
474
+ chipOpen.value = false;
475
+ if (focusChip) root.value?.querySelector(`#${chipAnchorId}`)?.focus();
476
+ };
438
477
  const slashExtension = createLoomaSlashCommandExtension({
439
478
  onOpenImagePicker: () => fileInput.value?.click(),
479
+ onOpenChipEditor: openChipEditor,
440
480
  onStateChange: (state) => {
441
481
  Object.assign(slash, state);
442
482
  }
@@ -602,7 +642,13 @@ var LoomaEditor = defineComponent({
602
642
  instance.view.dispatch(transaction);
603
643
  instance.commands.focus();
604
644
  };
605
- watch(() => props.editable, (editable) => editor.value?.setEditable(editable));
645
+ watch(() => props.editable, (editable) => {
646
+ editor.value?.setEditable(editable);
647
+ root.value?.querySelectorAll("[data-looma-chip]").forEach((chip) => {
648
+ chip.setAttribute("aria-disabled", String(!editable));
649
+ });
650
+ if (!editable) chipOpen.value = false;
651
+ });
606
652
  watch(() => props.label, (label) => {
607
653
  editor.value?.setOptions({ editorProps: { attributes: {
608
654
  role: "textbox",
@@ -796,6 +842,18 @@ var LoomaEditor = defineComponent({
796
842
  trigger
797
843
  });
798
844
  };
845
+ const chipElementForEvent = (event) => {
846
+ const element = event.target instanceof Element ? event.target.closest("[data-looma-chip]") : null;
847
+ return element && root.value?.contains(element) ? element : null;
848
+ };
849
+ const onChipClickCapture = (event) => {
850
+ const chip = chipElementForEvent(event);
851
+ if (chip && editor.value && props.editable) {
852
+ event.preventDefault();
853
+ event.stopPropagation();
854
+ openChipEditor(editor.value, editor.value.view.posAtDOM(chip, 0));
855
+ }
856
+ };
799
857
  const onImageClick = (event) => {
800
858
  const element = imageElementForEvent(event);
801
859
  if (element && !props.editable) activateImage(element, "pointer");
@@ -806,6 +864,12 @@ var LoomaEditor = defineComponent({
806
864
  };
807
865
  const onImageKeyDown = (event) => {
808
866
  if (event.key !== "Enter" && event.key !== " ") return;
867
+ const chip = chipElementForEvent(event);
868
+ if (chip && editor.value && props.editable) {
869
+ event.preventDefault();
870
+ openChipEditor(editor.value, editor.value.view.posAtDOM(chip, 0));
871
+ return;
872
+ }
809
873
  const element = imageElementForEvent(event);
810
874
  if (!element) return;
811
875
  event.preventDefault();
@@ -1149,6 +1213,7 @@ var LoomaEditor = defineComponent({
1149
1213
  }, 100);
1150
1214
  },
1151
1215
  onDrop,
1216
+ onClickCapture: onChipClickCapture,
1152
1217
  onClick: onImageClick,
1153
1218
  onDblclick: onImageDoubleClick,
1154
1219
  onKeydown: onImageKeyDown
@@ -1213,6 +1278,54 @@ var LoomaEditor = defineComponent({
1213
1278
  class: "looma-editor__drop-overlay",
1214
1279
  "aria-hidden": "true"
1215
1280
  }, [loomaIcon("image"), h("span", "Drop image to upload")]) : null,
1281
+ h(Popover, {
1282
+ class: "looma-editor__chip-popover",
1283
+ open: chipOpen.value,
1284
+ for: chipAnchorId,
1285
+ placement: mobile.value ? "top-start" : "bottom-start",
1286
+ onClose: () => {
1287
+ chipOpen.value = false;
1288
+ }
1289
+ }, () => h("div", {
1290
+ class: "looma-editor__chip-form",
1291
+ role: "dialog",
1292
+ "aria-label": "Edit chip"
1293
+ }, [h("input", {
1294
+ ref: chipInput,
1295
+ type: "text",
1296
+ value: chipLabel.value,
1297
+ placeholder: "Set a label",
1298
+ "aria-label": "Chip text",
1299
+ onInput: (event) => {
1300
+ chipLabel.value = event.target.value;
1301
+ changeChip({ label: chipLabel.value });
1302
+ },
1303
+ onKeydown: (event) => {
1304
+ if (event.key === "Enter" || event.key === "Escape") {
1305
+ event.preventDefault();
1306
+ closeChip(true);
1307
+ }
1308
+ }
1309
+ }), h("div", {
1310
+ class: "looma-editor__chip-colors",
1311
+ role: "group",
1312
+ "aria-label": "Chip color"
1313
+ }, LOOMA_CHIP_COLORS.map((color) => h("button", {
1314
+ type: "button",
1315
+ class: "looma-editor__chip-color",
1316
+ "data-color": color,
1317
+ "aria-label": `${color[0].toUpperCase()}${color.slice(1)} chip`,
1318
+ "aria-pressed": chipColor.value === color,
1319
+ onClick: () => {
1320
+ if (chipColor.value === color) {
1321
+ chipInput.value?.focus();
1322
+ return;
1323
+ }
1324
+ chipColor.value = color;
1325
+ changeChip({ color });
1326
+ chipInput.value?.focus();
1327
+ }
1328
+ }, chipColor.value === color ? "✓" : "")))])),
1216
1329
  h(Popover, {
1217
1330
  class: "looma-editor__link-popover",
1218
1331
  open: linkOpen.value,