{"version":3,"file":"ngxtension-inject-text-selection.mjs","sources":["../../../../libs/ngxtension/inject-text-selection/src/inject-text-selection.ts","../../../../libs/ngxtension/inject-text-selection/src/ngxtension-inject-text-selection.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport {\n\tcomputed,\n\tDestroyRef,\n\tinject,\n\tInjectable,\n\tsignal,\n} from '@angular/core';\nimport { explicitEffect } from 'ngxtension/explicit-effect';\n\n@Injectable({ providedIn: 'root' })\nexport class TextSelectionService {\n\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\treadonly window = inject(DOCUMENT).defaultView!;\n\n\t// A writable signal to store number of listeners\n\treadonly listeners = signal<number>(0);\n\n\t// A writable signal to store the current Selection (or null).\n\treadonly selection = signal<Selection | null>(null);\n\n\t// A computed signal that returns the current selected text.\n\treadonly text = computed(() =>\n\t\tthis.selection() ? this.selection()?.toString() : '',\n\t);\n\n\t// A computed signal that returns the list of Range objects (if any).\n\treadonly ranges = computed(() =>\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tthis.selection() ? getRangesFromSelection(this.selection()!) : [],\n\t);\n\n\t// A computed signal that maps each Range to its bounding client rect.\n\treadonly rects = computed(() =>\n\t\tthis.ranges().map((range) => range.getBoundingClientRect()),\n\t);\n\n\tprivate isListening = false;\n\n\tconstructor() {\n\t\t/**\n\t\t * Handler for the 'selectionchange' event.\n\t\t * We first clear the signal, then update it with the latest selection.\n\t\t */\n\t\tconst onSelectionChange = () => {\n\t\t\tthis.selection.set(null);\n\t\t\tif (this.window) {\n\t\t\t\tthis.selection.set(this.window.getSelection());\n\t\t\t}\n\t\t};\n\n\t\texplicitEffect([this.listeners], ([listeners]) => {\n\t\t\tif (listeners === 0 && this.isListening) {\n\t\t\t\t// if we don't have any listeners anymore, we need to remove the event listener\n\t\t\t\tthis.window.document.removeEventListener(\n\t\t\t\t\t'selectionchange',\n\t\t\t\t\tonSelectionChange,\n\t\t\t\t);\n\t\t\t\tthis.isListening = false;\n\t\t\t}\n\n\t\t\tif (listeners > 0 && !this.isListening) {\n\t\t\t\tthis.window.document.addEventListener(\n\t\t\t\t\t'selectionchange',\n\t\t\t\t\tonSelectionChange,\n\t\t\t\t\t{\n\t\t\t\t\t\tpassive: true,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.isListening = true;\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Clears the selection. This is a convenience method for `window.getSelection().empty()`.\n\t */\n\tresetSelection() {\n\t\tthis.window.getSelection()?.empty();\n\t}\n}\n\n/**\n * Returns an array of Range objects from a Selection.\n */\nfunction getRangesFromSelection(selection: Selection): Range[] {\n\tconst rangeCount = selection.rangeCount ?? 0;\n\treturn Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));\n}\n\n/**\n * Creates reactive signals for text selection.\n *\n * Example:\n * ```ts\n * const selection = injectTextSelection();\n *\n * selection.text() // returns the selected text\n * selection.rects() // returns an array of bounding rects for each selection range\n * selection.ranges() // returns an array of Range objects for each selection range\n * selection.selection() // returns the Selection object\n * selection.clearSelection() // clears the selection\n * ```\n *\n * @returns An object with signals for the selected text, selection ranges, rects, and the raw selection.\n */\nexport function injectTextSelection() {\n\tconst textSelectionService = inject(TextSelectionService);\n\tconst destroyRef = inject(DestroyRef);\n\n\t// we want to increase the listeners count when the component is created and decrease it when it is destroyed\n\t// this is to ensure that we only add the event listener when there are listeners\n\ttextSelectionService.listeners.update((x) => x + 1);\n\tdestroyRef.onDestroy(() =>\n\t\ttextSelectionService.listeners.update((x) => x - 1),\n\t);\n\n\treturn {\n\t\ttext: textSelectionService.text,\n\t\trects: textSelectionService.rects,\n\t\tranges: textSelectionService.ranges,\n\t\tselection: textSelectionService.selection.asReadonly(),\n\t\tclearSelection: () => textSelectionService.resetSelection(),\n\t};\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}