@bsky.app/tapper 0.5.1 → 0.5.2
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.
- package/CHANGELOG.md +6 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -3
- package/build/index.js.map +1 -1
- package/eslint.config.js +4 -0
- package/package.json +2 -1
- package/src/index.ts +5 -3
- package/vitest.config.ts +6 -0
- package/.eslintrc.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @bsky.app/tapper
|
|
2
2
|
|
|
3
|
+
## 0.5.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#30](https://github.com/bluesky-social/toolbox/pull/30) [`846da1c`](https://github.com/bluesky-social/toolbox/commit/846da1c734113a058fac16693c0936d2d74b621b) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Do not atomic-delete URL facets
|
|
8
|
+
|
|
3
9
|
## 0.5.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,SAAS,EAAC,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,SAAS,EAAC,MAAM,cAAc,CAAA;AAGhD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAA;AAUhB,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,MAAM,MAAM,UAAU,CAAA;AAIlC,qBAAa,MAAM;IACjB,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAqB;IAIrC,OAAO,CAAC,SAAS,CAA0D;IAG3E,IAAI,SAAK;IACT,SAAS,EAAE,eAAe,CAAqB;IAC/C,KAAK,EAAE,UAAU,EAAE,CAAK;IACxB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAO;IAE5C,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,QAAQ,CAAQ;IAGxB,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,MAAM,CAAC,EAAE,YAAY;IAejC,WAAW,GAAI,MAAM,SAAS,GAAG,IAAI,UAEpC;IAED,OAAO,CAAC,iBAAiB;IAUzB,SAAS,GAAI,UAAU,MAAM,IAAI,mBAGhC;IAED,WAAW,QAAO,cAAc,CAE/B;IAED,EAAE,GAAI,CAAC,SAAS,MAAM,YAAY,EAChC,OAAO,CAAC,EACR,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,gBAOpC;IAED,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,MAAM;IAgFd,gBAAgB,GAAI,SAAS,MAAM,UAkClC;IAED,qBAAqB,GAAI,GAAG;QAC1B,WAAW,EAAE;YAAC,SAAS,EAAE;gBAAC,KAAK,EAAE,MAAM,CAAC;gBAAC,GAAG,EAAE,MAAM,CAAA;aAAC,CAAA;SAAC,CAAA;KACvD,UA+BA;IAED,WAAW,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,UA+B3C;IAED,OAAO,CAAC,OAAO,CAsBd;IAED,MAAM,GAAI,MAAM,MAAM,UAMrB;CACF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY;;SAlPtC,CAAC,SAAS,MAAM,YAAY,2CAED,IAAI;mBAuOrB,MAAM;;;;;;;oBAgBb,SAAS,GAAG,IAAI;;gCAhJI,MAAM;+BAoCP;YAC1B,WAAW,EAAE;gBAAC,SAAS,EAAE;oBAAC,KAAK,EAAE,MAAM,CAAC;oBAAC,GAAG,EAAE,MAAM,CAAA;iBAAC,CAAA;aAAC,CAAA;SACvD;;EAoIF"}
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useRef, useState, useSyncExternalStore } from 'react';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
|
-
import { parseNodesFromText, detectActiveFacet, deriveTriggers, nodeToFacet, compileFacetRegexes, } from './util';
|
|
4
3
|
import * as defaultFacets from './facets';
|
|
4
|
+
import { parseNodesFromText, detectActiveFacet, deriveTriggers, nodeToFacet, compileFacetRegexes, } from './util';
|
|
5
5
|
export * from './types';
|
|
6
6
|
export * as facets from './facets';
|
|
7
7
|
const IS_WEB = Platform.OS === 'web';
|
|
@@ -9,7 +9,6 @@ export class Tapper {
|
|
|
9
9
|
facetRegexes;
|
|
10
10
|
triggers;
|
|
11
11
|
// Event emitters
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
12
|
listeners = new Map();
|
|
14
13
|
// Public state
|
|
15
14
|
text = '';
|
|
@@ -140,11 +139,13 @@ export class Tapper {
|
|
|
140
139
|
return;
|
|
141
140
|
const diff = newText.length - this.text.length;
|
|
142
141
|
let newEnd = Math.max(this.selection.end + diff, 0);
|
|
143
|
-
// Atomic deletion: backspace into a facet from outside
|
|
142
|
+
// Atomic deletion: backspace into a facet from outside.
|
|
143
|
+
// URLs are excluded — users typically want to edit them character by character.
|
|
144
144
|
let didAtomicDelete = false;
|
|
145
145
|
if (diff === -1 && newEnd < this.selection.end) {
|
|
146
146
|
for (const node of this.nodes) {
|
|
147
147
|
if (node.type === 'facet' &&
|
|
148
|
+
node.facetType !== 'url' &&
|
|
148
149
|
node.committed &&
|
|
149
150
|
this.selection.end === node.end) {
|
|
150
151
|
const remnant = node.end - node.start - 1;
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useRef, useState, useSyncExternalStore} from 'react'\nimport {Platform, TextInput} from 'react-native'\n\nimport {\n TapperConfig,\n TapperEvents,\n TapperNode,\n TapperActiveFacet,\n TapperSelection,\n TapperSnapshot,\n} from './types'\nimport {\n parseNodesFromText,\n detectActiveFacet,\n deriveTriggers,\n nodeToFacet,\n compileFacetRegexes,\n type CompiledFacetRegexes,\n} from './util'\nimport * as defaultFacets from './facets'\n\nexport * from './types'\nexport * as facets from './facets'\n\nconst IS_WEB = Platform.OS === 'web'\n\nexport class Tapper {\n private facetRegexes: CompiledFacetRegexes\n private triggers: Map<string, string>\n\n // Event emitters\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private listeners = new Map<keyof TapperEvents, Set<(data: any) => void>>()\n\n // Public state\n text = ''\n selection: TapperSelection = {start: 0, end: 0}\n nodes: TapperNode[] = []\n activeFacet: TapperActiveFacet | null = null\n\n private inputRef: TextInput | null = null\n private updating = false\n\n // useSyncExternalStore plumbing\n private storeListeners = new Set<() => void>()\n private snapshot: TapperSnapshot\n\n constructor(config?: TapperConfig) {\n const facets = config?.facets || defaultFacets\n this.facetRegexes = compileFacetRegexes(facets)\n this.triggers = deriveTriggers(facets)\n this.snapshot = {\n text: this.text,\n selection: this.selection,\n nodes: this.nodes,\n activeFacet: this.activeFacet,\n }\n if (config?.initialText) {\n this.replaceText(config.initialText)\n }\n }\n\n setInputRef = (node: TextInput | null) => {\n this.inputRef = node\n }\n\n private setInputSelection(start: number, end: number) {\n const el = this.inputRef as any\n if (!el) return\n if (typeof el.setSelection === 'function') {\n el.setSelection(start, end)\n } else if (typeof el.setSelectionRange === 'function') {\n el.setSelectionRange(start, end)\n }\n }\n\n subscribe = (listener: () => void) => {\n this.storeListeners.add(listener)\n return () => this.storeListeners.delete(listener)\n }\n\n getSnapshot = (): TapperSnapshot => {\n return this.snapshot\n }\n\n on = <K extends keyof TapperEvents>(\n event: K,\n cb: (data: TapperEvents[K]) => void,\n ) => {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set())\n this.listeners.get(event)!.add(cb)\n return () => {\n this.listeners.get(event)?.delete(cb)\n }\n }\n\n private emit<K extends keyof TapperEvents>(event: K, data: TapperEvents[K]) {\n this.listeners.get(event)?.forEach(cb => cb(data))\n }\n\n private notify() {\n this.snapshot = {\n text: this.text,\n selection: this.selection,\n nodes: this.nodes,\n activeFacet: this.activeFacet,\n }\n this.storeListeners.forEach(l => l())\n }\n\n private update(\n newText: string,\n selection: TapperSelection,\n commitAll?: boolean,\n ) {\n // guard against circular updates when insert() is called during an update cycle\n if (this.updating) return\n this.updating = true\n\n const cursor = selection.end\n const isRange = selection.start !== selection.end\n\n const nodes =\n newText === this.text\n ? this.nodes\n : parseNodesFromText(\n newText,\n this.facetRegexes,\n this.nodes,\n cursor,\n this.triggers,\n )\n const prev = this.activeFacet\n const detected =\n isRange || commitAll\n ? null\n : detectActiveFacet(nodes, newText, cursor, this.triggers)\n\n // When cursor leaves a facet, commit it.\n // On paste (commitAll), commit every uncommitted facet.\n const committedNodes: TapperNode[] = []\n if (commitAll) {\n for (const node of nodes) {\n if (node.type === 'facet' && !node.committed) {\n node.committed = true\n committedNodes.push(node)\n }\n }\n } else if (prev && (!detected || detected.type !== prev.type)) {\n for (const node of nodes) {\n if (\n node.type === 'facet' &&\n node.facetType === prev.type &&\n node.start === prev.range.start &&\n node.end === prev.range.end\n ) {\n node.committed = true\n committedNodes.push(node)\n break\n }\n }\n }\n\n // Build activeFacet with insert() baked in\n const active: TapperActiveFacet | null = detected\n ? {...detected, replace: this.replace}\n : null\n\n this.text = newText\n this.selection = selection\n this.nodes = nodes\n this.activeFacet = active\n\n this.updating = false\n this.notify()\n\n // Fire events after state is finalized\n const facetChanged =\n active?.type !== prev?.type ||\n active?.value !== prev?.value ||\n active?.range.start !== prev?.range.start ||\n active?.range.end !== prev?.range.end\n if (facetChanged) {\n this.emit('activeFacet', active)\n }\n for (const node of committedNodes) {\n this.emit('facetCommitted', nodeToFacet(node))\n }\n }\n\n handleTextChange = (newText: string) => {\n if (newText === this.text) return\n\n const diff = newText.length - this.text.length\n let newEnd = Math.max(this.selection.end + diff, 0)\n\n // Atomic deletion: backspace into a facet from outside\n let didAtomicDelete = false\n if (diff === -1 && newEnd < this.selection.end) {\n for (const node of this.nodes) {\n if (\n node.type === 'facet' &&\n node.committed &&\n this.selection.end === node.end\n ) {\n const remnant = node.end - node.start - 1\n newText =\n newText.slice(0, node.start) + newText.slice(node.start + remnant)\n newEnd = node.start\n didAtomicDelete = true\n break\n }\n }\n }\n\n // Paste: commit all facets immediately\n const mayBePaste = diff > 1\n\n this.update(newText, {start: newEnd, end: newEnd}, mayBePaste)\n if (didAtomicDelete) {\n this.setInputSelection(newEnd, newEnd)\n }\n }\n\n handleSelectionChange = (e: {\n nativeEvent: {selection: {start: number; end: number}}\n }) => {\n const {start, end} = e.nativeEvent.selection\n\n if (end > this.text.length) return\n\n if (start === this.selection.start && end === this.selection.end) return\n\n this.selection = {start, end}\n\n /*\n * For selection-only changes (no text change), check if the active facet\n * would change. If not, skip the full update() cycle — just update the\n * selection in the snapshot.\n */\n const isRange = start !== end\n const detected = isRange\n ? null\n : detectActiveFacet(this.nodes, this.text, end, this.triggers)\n const prev = this.activeFacet\n const facetChanged =\n detected?.type !== prev?.type ||\n detected?.value !== prev?.value ||\n detected?.range.start !== prev?.range.start ||\n detected?.range.end !== prev?.range.end\n if (!facetChanged) {\n this.snapshot = {...this.snapshot, selection: this.selection}\n this.storeListeners.forEach(l => l())\n return\n }\n\n this.update(this.text, {start, end})\n }\n\n replaceText = (text: string, cursor?: number) => {\n const prevActive = this.activeFacet\n const nodes = parseNodesFromText(text, this.facetRegexes)\n // Mark all non-text nodes as committed (pre-existing facets)\n const newlyCommitted: TapperNode[] = []\n for (const node of nodes) {\n if (node.type === 'facet') {\n node.committed = true\n newlyCommitted.push(node)\n }\n }\n\n const pos = cursor ?? text.length\n const detected = detectActiveFacet(nodes, text, pos, this.triggers)\n const active: TapperActiveFacet | null = detected\n ? {...detected, replace: this.replace}\n : null\n\n this.text = text\n this.selection = {start: pos, end: pos}\n this.nodes = nodes\n this.activeFacet = active\n\n this.notify()\n\n if (active !== prevActive) {\n this.emit('activeFacet', active)\n }\n for (const node of newlyCommitted) {\n this.emit('facetCommitted', nodeToFacet(node))\n }\n }\n\n private replace = (value: string, options?: {noTrailingSpace?: boolean}) => {\n if (!this.activeFacet) return\n\n const replacement = value + (options?.noTrailingSpace ? '' : ' ')\n const newText =\n this.text.slice(0, this.activeFacet.range.start) +\n replacement +\n this.text.slice(this.activeFacet.range.end)\n const newEnd = this.activeFacet.range.start + replacement.length\n\n if (this.activeFacet) {\n this.activeFacet.raw = value\n this.activeFacet.value = value\n this.activeFacet.range = {\n start: this.activeFacet.range.start,\n end: this.activeFacet.range.start + value.length,\n }\n this.emit('afterInsert', this.activeFacet)\n }\n\n this.update(newText, {start: newEnd, end: newEnd})\n this.setInputSelection(newEnd, newEnd)\n }\n\n insert = (text: string) => {\n const pos = this.selection.end\n const newText = this.text.slice(0, pos) + text + this.text.slice(pos)\n const newEnd = pos + text.length\n this.update(newText, {start: newEnd, end: newEnd})\n this.setInputSelection(newEnd, newEnd)\n }\n}\n\nexport function useTapper(config: TapperConfig) {\n const [store] = useState(() => new Tapper(config))\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot)\n const [input, setInput] = useState<TextInput | null>(null)\n const inputRef = useRef<{focus(): void}>(null)\n\n const handleSetInput = useCallback(\n (node: TextInput | null) => {\n inputRef.current = node\n store.setInputRef(node)\n setInput(node)\n },\n [store],\n )\n const focus = useCallback(() => input?.focus(), [input])\n const blur = useCallback(() => input?.blur(), [input])\n\n return {\n state,\n on: store.on,\n insert: store.insert,\n input: {\n element: input,\n focus,\n blur,\n },\n inputProps: {\n ref: handleSetInput,\n value: IS_WEB ? state.text : undefined,\n onChangeText: store.handleTextChange,\n onSelectionChange: store.handleSelectionChange,\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useRef, useState, useSyncExternalStore} from 'react'\nimport {Platform, TextInput} from 'react-native'\n\nimport * as defaultFacets from './facets'\nimport {\n TapperConfig,\n TapperEvents,\n TapperNode,\n TapperActiveFacet,\n TapperSelection,\n TapperSnapshot,\n} from './types'\nimport {\n parseNodesFromText,\n detectActiveFacet,\n deriveTriggers,\n nodeToFacet,\n compileFacetRegexes,\n type CompiledFacetRegexes,\n} from './util'\n\nexport * from './types'\nexport * as facets from './facets'\n\nconst IS_WEB = Platform.OS === 'web'\n\nexport class Tapper {\n private facetRegexes: CompiledFacetRegexes\n private triggers: Map<string, string>\n\n // Event emitters\n\n private listeners = new Map<keyof TapperEvents, Set<(data: any) => void>>()\n\n // Public state\n text = ''\n selection: TapperSelection = {start: 0, end: 0}\n nodes: TapperNode[] = []\n activeFacet: TapperActiveFacet | null = null\n\n private inputRef: TextInput | null = null\n private updating = false\n\n // useSyncExternalStore plumbing\n private storeListeners = new Set<() => void>()\n private snapshot: TapperSnapshot\n\n constructor(config?: TapperConfig) {\n const facets = config?.facets || defaultFacets\n this.facetRegexes = compileFacetRegexes(facets)\n this.triggers = deriveTriggers(facets)\n this.snapshot = {\n text: this.text,\n selection: this.selection,\n nodes: this.nodes,\n activeFacet: this.activeFacet,\n }\n if (config?.initialText) {\n this.replaceText(config.initialText)\n }\n }\n\n setInputRef = (node: TextInput | null) => {\n this.inputRef = node\n }\n\n private setInputSelection(start: number, end: number) {\n const el = this.inputRef as any\n if (!el) return\n if (typeof el.setSelection === 'function') {\n el.setSelection(start, end)\n } else if (typeof el.setSelectionRange === 'function') {\n el.setSelectionRange(start, end)\n }\n }\n\n subscribe = (listener: () => void) => {\n this.storeListeners.add(listener)\n return () => this.storeListeners.delete(listener)\n }\n\n getSnapshot = (): TapperSnapshot => {\n return this.snapshot\n }\n\n on = <K extends keyof TapperEvents>(\n event: K,\n cb: (data: TapperEvents[K]) => void,\n ) => {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set())\n this.listeners.get(event)!.add(cb)\n return () => {\n this.listeners.get(event)?.delete(cb)\n }\n }\n\n private emit<K extends keyof TapperEvents>(event: K, data: TapperEvents[K]) {\n this.listeners.get(event)?.forEach(cb => cb(data))\n }\n\n private notify() {\n this.snapshot = {\n text: this.text,\n selection: this.selection,\n nodes: this.nodes,\n activeFacet: this.activeFacet,\n }\n this.storeListeners.forEach(l => l())\n }\n\n private update(\n newText: string,\n selection: TapperSelection,\n commitAll?: boolean,\n ) {\n // guard against circular updates when insert() is called during an update cycle\n if (this.updating) return\n this.updating = true\n\n const cursor = selection.end\n const isRange = selection.start !== selection.end\n\n const nodes =\n newText === this.text\n ? this.nodes\n : parseNodesFromText(\n newText,\n this.facetRegexes,\n this.nodes,\n cursor,\n this.triggers,\n )\n const prev = this.activeFacet\n const detected =\n isRange || commitAll\n ? null\n : detectActiveFacet(nodes, newText, cursor, this.triggers)\n\n // When cursor leaves a facet, commit it.\n // On paste (commitAll), commit every uncommitted facet.\n const committedNodes: TapperNode[] = []\n if (commitAll) {\n for (const node of nodes) {\n if (node.type === 'facet' && !node.committed) {\n node.committed = true\n committedNodes.push(node)\n }\n }\n } else if (prev && (!detected || detected.type !== prev.type)) {\n for (const node of nodes) {\n if (\n node.type === 'facet' &&\n node.facetType === prev.type &&\n node.start === prev.range.start &&\n node.end === prev.range.end\n ) {\n node.committed = true\n committedNodes.push(node)\n break\n }\n }\n }\n\n // Build activeFacet with insert() baked in\n const active: TapperActiveFacet | null = detected\n ? {...detected, replace: this.replace}\n : null\n\n this.text = newText\n this.selection = selection\n this.nodes = nodes\n this.activeFacet = active\n\n this.updating = false\n this.notify()\n\n // Fire events after state is finalized\n const facetChanged =\n active?.type !== prev?.type ||\n active?.value !== prev?.value ||\n active?.range.start !== prev?.range.start ||\n active?.range.end !== prev?.range.end\n if (facetChanged) {\n this.emit('activeFacet', active)\n }\n for (const node of committedNodes) {\n this.emit('facetCommitted', nodeToFacet(node))\n }\n }\n\n handleTextChange = (newText: string) => {\n if (newText === this.text) return\n\n const diff = newText.length - this.text.length\n let newEnd = Math.max(this.selection.end + diff, 0)\n\n // Atomic deletion: backspace into a facet from outside.\n // URLs are excluded — users typically want to edit them character by character.\n let didAtomicDelete = false\n if (diff === -1 && newEnd < this.selection.end) {\n for (const node of this.nodes) {\n if (\n node.type === 'facet' &&\n node.facetType !== 'url' &&\n node.committed &&\n this.selection.end === node.end\n ) {\n const remnant = node.end - node.start - 1\n newText =\n newText.slice(0, node.start) + newText.slice(node.start + remnant)\n newEnd = node.start\n didAtomicDelete = true\n break\n }\n }\n }\n\n // Paste: commit all facets immediately\n const mayBePaste = diff > 1\n\n this.update(newText, {start: newEnd, end: newEnd}, mayBePaste)\n if (didAtomicDelete) {\n this.setInputSelection(newEnd, newEnd)\n }\n }\n\n handleSelectionChange = (e: {\n nativeEvent: {selection: {start: number; end: number}}\n }) => {\n const {start, end} = e.nativeEvent.selection\n\n if (end > this.text.length) return\n\n if (start === this.selection.start && end === this.selection.end) return\n\n this.selection = {start, end}\n\n /*\n * For selection-only changes (no text change), check if the active facet\n * would change. If not, skip the full update() cycle — just update the\n * selection in the snapshot.\n */\n const isRange = start !== end\n const detected = isRange\n ? null\n : detectActiveFacet(this.nodes, this.text, end, this.triggers)\n const prev = this.activeFacet\n const facetChanged =\n detected?.type !== prev?.type ||\n detected?.value !== prev?.value ||\n detected?.range.start !== prev?.range.start ||\n detected?.range.end !== prev?.range.end\n if (!facetChanged) {\n this.snapshot = {...this.snapshot, selection: this.selection}\n this.storeListeners.forEach(l => l())\n return\n }\n\n this.update(this.text, {start, end})\n }\n\n replaceText = (text: string, cursor?: number) => {\n const prevActive = this.activeFacet\n const nodes = parseNodesFromText(text, this.facetRegexes)\n // Mark all non-text nodes as committed (pre-existing facets)\n const newlyCommitted: TapperNode[] = []\n for (const node of nodes) {\n if (node.type === 'facet') {\n node.committed = true\n newlyCommitted.push(node)\n }\n }\n\n const pos = cursor ?? text.length\n const detected = detectActiveFacet(nodes, text, pos, this.triggers)\n const active: TapperActiveFacet | null = detected\n ? {...detected, replace: this.replace}\n : null\n\n this.text = text\n this.selection = {start: pos, end: pos}\n this.nodes = nodes\n this.activeFacet = active\n\n this.notify()\n\n if (active !== prevActive) {\n this.emit('activeFacet', active)\n }\n for (const node of newlyCommitted) {\n this.emit('facetCommitted', nodeToFacet(node))\n }\n }\n\n private replace = (value: string, options?: {noTrailingSpace?: boolean}) => {\n if (!this.activeFacet) return\n\n const replacement = value + (options?.noTrailingSpace ? '' : ' ')\n const newText =\n this.text.slice(0, this.activeFacet.range.start) +\n replacement +\n this.text.slice(this.activeFacet.range.end)\n const newEnd = this.activeFacet.range.start + replacement.length\n\n if (this.activeFacet) {\n this.activeFacet.raw = value\n this.activeFacet.value = value\n this.activeFacet.range = {\n start: this.activeFacet.range.start,\n end: this.activeFacet.range.start + value.length,\n }\n this.emit('afterInsert', this.activeFacet)\n }\n\n this.update(newText, {start: newEnd, end: newEnd})\n this.setInputSelection(newEnd, newEnd)\n }\n\n insert = (text: string) => {\n const pos = this.selection.end\n const newText = this.text.slice(0, pos) + text + this.text.slice(pos)\n const newEnd = pos + text.length\n this.update(newText, {start: newEnd, end: newEnd})\n this.setInputSelection(newEnd, newEnd)\n }\n}\n\nexport function useTapper(config: TapperConfig) {\n const [store] = useState(() => new Tapper(config))\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot)\n const [input, setInput] = useState<TextInput | null>(null)\n const inputRef = useRef<{focus(): void}>(null)\n\n const handleSetInput = useCallback(\n (node: TextInput | null) => {\n inputRef.current = node\n store.setInputRef(node)\n setInput(node)\n },\n [store],\n )\n const focus = useCallback(() => input?.focus(), [input])\n const blur = useCallback(() => input?.blur(), [input])\n\n return {\n state,\n on: store.on,\n insert: store.insert,\n input: {\n element: input,\n focus,\n blur,\n },\n inputProps: {\n ref: handleSetInput,\n value: IS_WEB ? state.text : undefined,\n onChangeText: store.handleTextChange,\n onSelectionChange: store.handleSelectionChange,\n },\n }\n}\n"]}
|
package/eslint.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsky.app/tapper",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A minimal rich text editor for React Native and web.",
|
|
6
6
|
"repository": "https://github.com/bluesky-social/toolbox",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"build": "expo-module clean && expo-module build",
|
|
32
32
|
"clean": "expo-module clean",
|
|
33
33
|
"lint": "expo-module lint",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
34
35
|
"test": "vitest run",
|
|
35
36
|
"expo-module": "expo-module",
|
|
36
37
|
"open:ios": "xed example/ios",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {useCallback, useRef, useState, useSyncExternalStore} from 'react'
|
|
2
2
|
import {Platform, TextInput} from 'react-native'
|
|
3
3
|
|
|
4
|
+
import * as defaultFacets from './facets'
|
|
4
5
|
import {
|
|
5
6
|
TapperConfig,
|
|
6
7
|
TapperEvents,
|
|
@@ -17,7 +18,6 @@ import {
|
|
|
17
18
|
compileFacetRegexes,
|
|
18
19
|
type CompiledFacetRegexes,
|
|
19
20
|
} from './util'
|
|
20
|
-
import * as defaultFacets from './facets'
|
|
21
21
|
|
|
22
22
|
export * from './types'
|
|
23
23
|
export * as facets from './facets'
|
|
@@ -29,7 +29,7 @@ export class Tapper {
|
|
|
29
29
|
private triggers: Map<string, string>
|
|
30
30
|
|
|
31
31
|
// Event emitters
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
private listeners = new Map<keyof TapperEvents, Set<(data: any) => void>>()
|
|
34
34
|
|
|
35
35
|
// Public state
|
|
@@ -194,12 +194,14 @@ export class Tapper {
|
|
|
194
194
|
const diff = newText.length - this.text.length
|
|
195
195
|
let newEnd = Math.max(this.selection.end + diff, 0)
|
|
196
196
|
|
|
197
|
-
// Atomic deletion: backspace into a facet from outside
|
|
197
|
+
// Atomic deletion: backspace into a facet from outside.
|
|
198
|
+
// URLs are excluded — users typically want to edit them character by character.
|
|
198
199
|
let didAtomicDelete = false
|
|
199
200
|
if (diff === -1 && newEnd < this.selection.end) {
|
|
200
201
|
for (const node of this.nodes) {
|
|
201
202
|
if (
|
|
202
203
|
node.type === 'facet' &&
|
|
204
|
+
node.facetType !== 'url' &&
|
|
203
205
|
node.committed &&
|
|
204
206
|
this.selection.end === node.end
|
|
205
207
|
) {
|
package/vitest.config.ts
CHANGED
|
@@ -3,5 +3,11 @@ import {defineConfig} from 'vitest/config'
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
5
|
include: ['src/__tests__/**/*.test.ts'],
|
|
6
|
+
alias: {
|
|
7
|
+
'react-native': new URL(
|
|
8
|
+
'src/__tests__/__mocks__/react-native.ts',
|
|
9
|
+
import.meta.url,
|
|
10
|
+
).pathname,
|
|
11
|
+
},
|
|
6
12
|
},
|
|
7
13
|
})
|