@threadlabs/looma 0.28.0 → 0.28.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.
@@ -0,0 +1,25 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import { createLowlight } from "lowlight";
3
+ import type { LoomaCodeLanguages } from "./extensions/preset.js";
4
+ /** Code-block highlighting for one editor: its lowlight registry, picker languages, and grammar loading. */
5
+ export interface LoomaCodeHighlighting {
6
+ /** The registry the code-block extension highlights with. */
7
+ readonly lowlight: ReturnType<typeof createLowlight>;
8
+ /** Languages the picker offers, in display order. */
9
+ readonly languageNames: readonly string[];
10
+ /**
11
+ * Loads the grammar a code block needs, then re-highlights the document.
12
+ *
13
+ * @contract Does nothing when the host supplied its own languages; each grammar loads once.
14
+ * @failure A failed import is forgotten so a later call retries it.
15
+ */
16
+ loadForCodeBlock(editor: Editor, language: unknown, value: string): void;
17
+ }
18
+ /**
19
+ * Highlighting from the host's `codeLanguages`, or, when that is null, from the built-in catalog,
20
+ * whose grammars load on demand and whose automatic detection uses a common subset.
21
+ *
22
+ * @lifecycle `onGrammarsLoaded` runs after each lazy load, before the document is re-highlighted,
23
+ * because the lowlight registry is mutable rather than reactive.
24
+ */
25
+ export declare function createCodeHighlighting(codeLanguages: LoomaCodeLanguages | null, onGrammarsLoaded: () => void): LoomaCodeHighlighting;
@@ -0,0 +1,10 @@
1
+ import type { JSONContent } from "@tiptap/core";
2
+ /** Whether a controlled document already matches the editor, so applying it would only reset the selection. */
3
+ export declare function sameDocument(left: JSONContent, right: JSONContent): boolean;
4
+ /**
5
+ * A link destination the editor may store: a site-relative path, fragment, or query, or an
6
+ * absolute http, https, mailto, or tel URL.
7
+ *
8
+ * @failure Returns null for empty input, control characters, other schemes, and unparseable URLs.
9
+ */
10
+ export declare function validLinkHref(value: string): string | null;
@@ -1,5 +1,5 @@
1
1
  import { Extension, type Editor, type JSONContent } from "@tiptap/core";
2
- export { normalizeImageDimension } from "../../editor/extensions/image.js";
2
+ export { normalizeImageDimension } from "./extensions/image.js";
3
3
  /** What caused an overlay to open or close. */
4
4
  type OverlayTrigger = "keyboard" | "pointer" | "programmatic";
5
5
  /**
@@ -0,0 +1,8 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import type { SlashMenuAnchorRect } from "./slash-menu.js";
3
+ /** A plain copy of a client rectangle, the anchor shape the slash and mention menus accept as a prop. */
4
+ export declare function menuAnchorRect(rect: DOMRect | null): SlashMenuAnchorRect | null;
5
+ /** The rendered table that holds the selection's start. */
6
+ export declare function selectedTableElement(editor: Editor): HTMLTableElement | null;
7
+ /** The rendered cell that holds the selection's start. */
8
+ export declare function selectedTableCellElement(editor: Editor): HTMLTableCellElement | null;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
3
+ * it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
4
+ */
5
+ export declare function formatEditorShortcut(keys: string, apple: boolean): string;
6
+ export declare const isApplePlatform: () => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -3,7 +3,7 @@ import "./looma-editor.css";
3
3
  import { type AnyExtension, type Editor, type JSONContent } from "@tiptap/core";
4
4
  import { type PropType, type VNode } from "vue";
5
5
  import { type LoomaSlashCommand, type LoomaMentionItem, type LoomaMentionProvider, type LoomaCodeLanguages } from "@threadlabs/looma/editor";
6
- import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "./image-delivery.js";
6
+ import { type LoomaImageActivationDetail, type LoomaImageAttributeResolver, type LoomaImageDescriptor, type LoomaImageRenditionErrorDetail } from "../../editor/image-delivery.js";
7
7
  /**
8
8
  * Host upload result normalized into the editor's durable image descriptor.
9
9
  * `url` becomes the stored `src`; optional dimensions preserve layout before
@@ -368,10 +368,10 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
368
368
  onImageActivate?: ((_detail: LoomaImageActivationDetail) => any) | undefined;
369
369
  onImageRenditionError?: ((_detail: LoomaImageRenditionErrorDetail) => any) | undefined;
370
370
  }>, {
371
- label: string;
372
- placeholder: string;
373
371
  extensions: AnyExtension[];
374
372
  editable: boolean;
373
+ label: string;
374
+ placeholder: string;
375
375
  linkBaseUrl: string;
376
376
  disableHighlight: boolean;
377
377
  codeLanguages: Readonly<Record<string, import("highlight.js").LanguageFn>> | null;
@@ -62,10 +62,10 @@ export declare const LoomaCompactEditor: import("vue").DefineComponent<import("v
62
62
  "onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
63
63
  onReady?: ((_editor: Editor) => any) | undefined;
64
64
  }>, {
65
- label: string;
66
- id: string;
67
65
  extensions: AnyExtension[];
68
66
  editable: boolean;
67
+ label: string;
68
+ id: string;
69
69
  modelValue: JSONContent;
70
70
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
71
71
  /** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
@@ -1,4 +1,4 @@
1
1
  export * from "@threadlabs/looma/editor";
2
2
  export * from "./primitives.js";
3
3
  export { LoomaEditor, type LoomaEditorToolbarMode, type LoomaEditorSelectionAction, type LoomaEditorSelectionActionDetail, type LoomaEditorRangeAction, type LoomaEditorRangeActionDetail, type LoomaEditorSlashCommands, type LoomaLinkTarget, type LoomaLinkSearch, type LoomaLinkResolve, type LoomaImageUploader, type LoomaImageUploadResult, } from "./LoomaEditor.js";
4
- export type { LoomaImageActivationDetail, LoomaImageActivationTrigger, LoomaImageAttributeResolver, LoomaImageDescriptor, LoomaImageRenditionErrorDetail, LoomaImageRenditionAttributes, } from "./image-delivery.js";
4
+ export type { LoomaImageActivationDetail, LoomaImageActivationTrigger, LoomaImageAttributeResolver, LoomaImageDescriptor, LoomaImageRenditionErrorDetail, LoomaImageRenditionAttributes, } from "../../editor/image-delivery.js";
@@ -4,9 +4,9 @@ import { Button, Callout, Card, Checkbox, Cluster, Combobox, Disclosure, EditorI
4
4
  import { Extension, Node as Node$1, getText, getTextSerializersFromSchema, mergeAttributes, nodeInputRule, posToDOMRect } from "@tiptap/core";
5
5
  import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
6
6
  import { closeHistory } from "@tiptap/pm/history";
7
- import { createLowlight } from "lowlight";
8
7
  import { AllSelection, NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
9
8
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
9
+ import { createLowlight } from "lowlight";
10
10
  import { DOMParser, DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
11
11
  export * from "@threadlabs/looma/editor";
12
12
  //#region src/vue/editor/primitives.ts
@@ -272,7 +272,7 @@ Image$1.extend({ addAttributes() {
272
272
  };
273
273
  } });
274
274
  //#endregion
275
- //#region src/vue/editor/image-delivery.ts
275
+ //#region src/editor/image-delivery.ts
276
276
  function optionalString(value) {
277
277
  return typeof value === "string" && value.length > 0 ? value : void 0;
278
278
  }
@@ -409,7 +409,7 @@ function imageUploadContent(image) {
409
409
  };
410
410
  }
411
411
  //#endregion
412
- //#region src/vue/editor/code-language-catalog.ts
412
+ //#region src/editor/code-language-catalog.ts
413
413
  var codeLanguageCatalog = {
414
414
  "1c": () => import("../chunks/1c.js").then((module) => module.default),
415
415
  "abnf": () => import("../chunks/abnf.js").then((module) => module.default),
@@ -628,6 +628,134 @@ var autoCodeLanguageNames = [
628
628
  "yaml"
629
629
  ];
630
630
  //#endregion
631
+ //#region src/editor/code-highlighting.ts
632
+ /**
633
+ * Highlighting from the host's `codeLanguages`, or, when that is null, from the built-in catalog,
634
+ * whose grammars load on demand and whose automatic detection uses a common subset.
635
+ *
636
+ * @lifecycle `onGrammarsLoaded` runs after each lazy load, before the document is re-highlighted,
637
+ * because the lowlight registry is mutable rather than reactive.
638
+ */
639
+ function createCodeHighlighting(codeLanguages, onGrammarsLoaded) {
640
+ const registry = createLowlight(codeLanguages ?? void 0);
641
+ const lowlight = codeLanguages === null ? {
642
+ ...registry,
643
+ highlightAuto: (value) => registry.highlightAuto(value, { subset: [...autoCodeLanguageNames] })
644
+ } : registry;
645
+ const languageNames = codeLanguages === null ? [...autoCodeLanguageNames, ...Object.keys(codeLanguageCatalog).filter((name) => !autoCodeLanguageNames.includes(name)).sort()] : Object.keys(codeLanguages).sort();
646
+ const grammarLoads = /* @__PURE__ */ new Map();
647
+ let catalogLoad;
648
+ const refresh = (editor) => {
649
+ if (editor.isDestroyed) return;
650
+ onGrammarsLoaded();
651
+ const tr = editor.state.tr;
652
+ editor.state.doc.descendants((node, pos) => {
653
+ if (node.type.name === "codeBlock") tr.setNodeMarkup(pos, void 0, node.attrs);
654
+ });
655
+ if (tr.docChanged) editor.view.dispatch(tr.setMeta("addToHistory", false));
656
+ };
657
+ const loadGrammar = (name) => {
658
+ const existing = grammarLoads.get(name);
659
+ if (existing) return existing;
660
+ const pending = codeLanguageCatalog[name]().then((grammar) => {
661
+ registry.register({ [name]: grammar });
662
+ }).catch((error) => {
663
+ grammarLoads.delete(name);
664
+ throw error;
665
+ });
666
+ grammarLoads.set(name, pending);
667
+ return pending;
668
+ };
669
+ return {
670
+ lowlight,
671
+ languageNames,
672
+ loadForCodeBlock(editor, language, value) {
673
+ if (codeLanguages !== null || editor.isDestroyed) return;
674
+ if (typeof language === "string" && Object.hasOwn(codeLanguageCatalog, language)) {
675
+ if (registry.registered(language) || grammarLoads.has(language)) return;
676
+ loadGrammar(language).then(() => refresh(editor)).catch(() => {});
677
+ } else if (!language && value.trim()) catalogLoad ??= Promise.all(autoCodeLanguageNames.map((name) => loadGrammar(name))).then(() => refresh(editor)).catch(() => {
678
+ catalogLoad = void 0;
679
+ });
680
+ }
681
+ };
682
+ }
683
+ //#endregion
684
+ //#region src/editor/content.ts
685
+ /** Whether a controlled document already matches the editor, so applying it would only reset the selection. */
686
+ function sameDocument(left, right) {
687
+ return JSON.stringify(left) === JSON.stringify(right);
688
+ }
689
+ /**
690
+ * A link destination the editor may store: a site-relative path, fragment, or query, or an
691
+ * absolute http, https, mailto, or tel URL.
692
+ *
693
+ * @failure Returns null for empty input, control characters, other schemes, and unparseable URLs.
694
+ */
695
+ function validLinkHref(value) {
696
+ const href = value.trim();
697
+ if (!href || /[\u0000-\u001f\u007f]/.test(href)) return null;
698
+ if (/^(\/|#|\?)/.test(href)) return href;
699
+ try {
700
+ const url = new URL(href);
701
+ return [
702
+ "http:",
703
+ "https:",
704
+ "mailto:",
705
+ "tel:"
706
+ ].includes(url.protocol) ? href : null;
707
+ } catch {
708
+ return null;
709
+ }
710
+ }
711
+ //#endregion
712
+ //#region src/editor/selection-geometry.ts
713
+ /** A plain copy of a client rectangle, the anchor shape the slash and mention menus accept as a prop. */
714
+ function menuAnchorRect(rect) {
715
+ if (!rect) return null;
716
+ return {
717
+ left: rect.left,
718
+ top: rect.top,
719
+ right: rect.right,
720
+ bottom: rect.bottom,
721
+ x: rect.x,
722
+ y: rect.y,
723
+ width: rect.width,
724
+ height: rect.height
725
+ };
726
+ }
727
+ function selectionElement(editor) {
728
+ const { node } = editor.view.domAtPos(editor.state.selection.from);
729
+ return node instanceof HTMLElement ? node : node.parentElement;
730
+ }
731
+ /** The rendered table that holds the selection's start. */
732
+ function selectedTableElement(editor) {
733
+ return selectionElement(editor)?.closest("table");
734
+ }
735
+ /** The rendered cell that holds the selection's start. */
736
+ function selectedTableCellElement(editor) {
737
+ return selectionElement(editor)?.closest("td, th");
738
+ }
739
+ //#endregion
740
+ //#region src/editor/shortcuts.ts
741
+ /**
742
+ * Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
743
+ * it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
744
+ */
745
+ function formatEditorShortcut(keys, apple) {
746
+ const parts = keys.split("-");
747
+ const key = (parts.pop() ?? "").toUpperCase();
748
+ const has = (name) => parts.includes(name);
749
+ if (apple) return `${has("Ctrl") ? "⌃" : ""}${has("Alt") ? "⌥" : ""}${has("Shift") ? "⇧" : ""}${has("Mod") ? "⌘" : ""}${key}`;
750
+ return [
751
+ has("Mod") || has("Ctrl") ? "Ctrl" : "",
752
+ has("Alt") ? "Alt" : "",
753
+ has("Shift") ? "Shift" : "",
754
+ key
755
+ ].filter(Boolean).join("+");
756
+ }
757
+ var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
758
+ //#endregion
631
759
  //#region src/editor/extensions/expand.ts
632
760
  /**
633
761
  * Reveals collapsed ancestors before a host navigates to a document position.
@@ -1669,23 +1797,6 @@ var imageNodeView = ({ editor, node, getPos, decorations }) => {
1669
1797
  };
1670
1798
  //#endregion
1671
1799
  //#region src/vue/editor/LoomaEditor.ts
1672
- /**
1673
- * Shows a key binding in ProseMirror notation ("Mod-Shift-8") the way the reader's platform writes
1674
- * it: modifier symbols in Apple's order on Apple devices (⇧⌘8), and Ctrl+Shift+8 elsewhere.
1675
- */
1676
- function formatEditorShortcut(keys, apple) {
1677
- const parts = keys.split("-");
1678
- const key = (parts.pop() ?? "").toUpperCase();
1679
- const has = (name) => parts.includes(name);
1680
- if (apple) return `${has("Ctrl") ? "⌃" : ""}${has("Alt") ? "⌥" : ""}${has("Shift") ? "⇧" : ""}${has("Mod") ? "⌘" : ""}${key}`;
1681
- return [
1682
- has("Mod") || has("Ctrl") ? "Ctrl" : "",
1683
- has("Alt") ? "Alt" : "",
1684
- has("Shift") ? "Shift" : "",
1685
- key
1686
- ].filter(Boolean).join("+");
1687
- }
1688
- var isApplePlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.userAgentData?.platform || navigator.platform || navigator.userAgent);
1689
1800
  var EMPTY_DOCUMENT = {
1690
1801
  type: "doc",
1691
1802
  content: []
@@ -1708,38 +1819,6 @@ var EMPTY_CAPABILITIES = {
1708
1819
  canMergeCells: false,
1709
1820
  canSplitCell: false
1710
1821
  };
1711
- function sameDocument(left, right) {
1712
- return JSON.stringify(left) === JSON.stringify(right);
1713
- }
1714
- function validLinkHref(value) {
1715
- const href = value.trim();
1716
- if (!href || /[\u0000-\u001f\u007f]/.test(href)) return null;
1717
- if (/^(\/|#|\?)/.test(href)) return href;
1718
- try {
1719
- const url = new URL(href);
1720
- return [
1721
- "http:",
1722
- "https:",
1723
- "mailto:",
1724
- "tel:"
1725
- ].includes(url.protocol) ? href : null;
1726
- } catch {
1727
- return null;
1728
- }
1729
- }
1730
- function managedMenuAnchorRect(rect) {
1731
- if (!rect) return null;
1732
- return {
1733
- left: rect.left,
1734
- top: rect.top,
1735
- right: rect.right,
1736
- bottom: rect.bottom,
1737
- x: rect.x,
1738
- y: rect.y,
1739
- width: rect.width,
1740
- height: rect.height
1741
- };
1742
- }
1743
1822
  function slashMenuRows(items) {
1744
1823
  const groups = [];
1745
1824
  for (const [index, item] of items.entries()) {
@@ -1760,14 +1839,6 @@ function slashMenuRows(items) {
1760
1839
  label
1761
1840
  }, () => rows)] : rows);
1762
1841
  }
1763
- function selectedTableElement(editor) {
1764
- const { node } = editor.view.domAtPos(editor.state.selection.from);
1765
- return (node instanceof HTMLElement ? node : node.parentElement)?.closest("table");
1766
- }
1767
- function selectedTableCellElement(editor) {
1768
- const { node } = editor.view.domAtPos(editor.state.selection.from);
1769
- return (node instanceof HTMLElement ? node : node.parentElement)?.closest("td, th");
1770
- }
1771
1842
  function loomaIcon(name) {
1772
1843
  return h("svg", {
1773
1844
  class: "looma-icon",
@@ -2144,12 +2215,10 @@ var LoomaEditor = defineComponent({
2144
2215
  };
2145
2216
  };
2146
2217
  const authorHighlight = !props.disableHighlight;
2147
- const codeLowlight = createLowlight(props.codeLanguages ?? void 0);
2148
- const codeHighlightLowlight = props.codeLanguages === null ? {
2149
- ...codeLowlight,
2150
- highlightAuto: (value) => codeLowlight.highlightAuto(value, { subset: [...autoCodeLanguageNames] })
2151
- } : codeLowlight;
2152
- const codeLanguageNames = props.codeLanguages === null ? [...autoCodeLanguageNames, ...Object.keys(codeLanguageCatalog).filter((name) => !autoCodeLanguageNames.includes(name)).sort()] : Object.keys(props.codeLanguages).sort();
2218
+ const codeHighlighting = createCodeHighlighting(props.codeLanguages, () => {
2219
+ codeUi.grammarVersion += 1;
2220
+ });
2221
+ const codeLanguageNames = codeHighlighting.languageNames;
2153
2222
  const codeUi = reactive({
2154
2223
  open: false,
2155
2224
  pos: 0,
@@ -2158,38 +2227,7 @@ var LoomaEditor = defineComponent({
2158
2227
  grammarVersion: 0,
2159
2228
  style: {}
2160
2229
  });
2161
- const grammarLoads = /* @__PURE__ */ new Map();
2162
- let catalogLoad;
2163
- const refreshCodeHighlights = (instance) => {
2164
- if (instance.isDestroyed) return;
2165
- codeUi.grammarVersion += 1;
2166
- const tr = instance.state.tr;
2167
- instance.state.doc.descendants((node, pos) => {
2168
- if (node.type.name === "codeBlock") tr.setNodeMarkup(pos, void 0, node.attrs);
2169
- });
2170
- if (tr.docChanged) instance.view.dispatch(tr.setMeta("addToHistory", false));
2171
- };
2172
- const loadGrammar = (name) => {
2173
- const existing = grammarLoads.get(name);
2174
- if (existing) return existing;
2175
- const pending = codeLanguageCatalog[name]().then((grammar) => {
2176
- codeLowlight.register({ [name]: grammar });
2177
- }).catch((error) => {
2178
- grammarLoads.delete(name);
2179
- throw error;
2180
- });
2181
- grammarLoads.set(name, pending);
2182
- return pending;
2183
- };
2184
- const loadForCodeBlock = (instance, language, value) => {
2185
- if (props.codeLanguages !== null || instance.isDestroyed) return;
2186
- if (typeof language === "string" && Object.hasOwn(codeLanguageCatalog, language)) {
2187
- if (codeLowlight.registered(language) || grammarLoads.has(language)) return;
2188
- loadGrammar(language).then(() => refreshCodeHighlights(instance)).catch(() => {});
2189
- } else if (!language && value.trim()) catalogLoad ??= Promise.all(autoCodeLanguageNames.map((name) => loadGrammar(name))).then(() => refreshCodeHighlights(instance)).catch(() => {
2190
- catalogLoad = void 0;
2191
- });
2192
- };
2230
+ const loadForCodeBlock = codeHighlighting.loadForCodeBlock;
2193
2231
  const editor = useEditor({
2194
2232
  extensions: [
2195
2233
  ...getDefaultEditorExtensions({
@@ -2198,7 +2236,7 @@ var LoomaEditor = defineComponent({
2198
2236
  mention: mentionExtension ?? false,
2199
2237
  disableHighlight: props.disableHighlight,
2200
2238
  codeLanguages: props.codeLanguages ?? void 0,
2201
- codeLowlight: codeHighlightLowlight,
2239
+ codeLowlight: codeHighlighting.lowlight,
2202
2240
  tableOfContentsNodeView,
2203
2241
  expandNodeView,
2204
2242
  dividerNodeView,
@@ -2823,7 +2861,7 @@ var LoomaEditor = defineComponent({
2823
2861
  };
2824
2862
  const codeLanguageOptions = computed(() => {
2825
2863
  codeUi.grammarVersion;
2826
- const detected = codeUi.open && codeUi.text.trim() ? codeHighlightLowlight.highlightAuto(codeUi.text).data?.language : null;
2864
+ const detected = codeUi.open && codeUi.text.trim() ? codeHighlighting.lowlight.highlightAuto(codeUi.text).data?.language : null;
2827
2865
  return [
2828
2866
  {
2829
2867
  value: "auto",
@@ -3836,7 +3874,7 @@ var LoomaEditor = defineComponent({
3836
3874
  open: true,
3837
3875
  query: slash.query,
3838
3876
  selectedIndex: slash.selectedIndex,
3839
- anchorRect: managedMenuAnchorRect(slash.rect),
3877
+ anchorRect: menuAnchorRect(slash.rect),
3840
3878
  onHighlight: (event) => {
3841
3879
  slash.highlight?.(event.detail.index);
3842
3880
  },
@@ -3857,7 +3895,7 @@ var LoomaEditor = defineComponent({
3857
3895
  query: mention.query,
3858
3896
  items: mention.items,
3859
3897
  selectedIndex: mention.selectedIndex,
3860
- anchorRect: managedMenuAnchorRect(mention.rect),
3898
+ anchorRect: menuAnchorRect(mention.rect),
3861
3899
  loading: mention.loading,
3862
3900
  onHighlight: (event) => {
3863
3901
  mention.selectedIndex = event.detail.index;