@bsky.app/tapper 0.3.1 → 0.3.3

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @bsky.app/tapper
2
2
 
3
+ ## 0.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e5e7985`](https://github.com/bluesky-social/toolbox/commit/e5e7985e312566fdf3272c82b24e4051e886e231) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Expose insert() on return from useTapper()
8
+
9
+ ## 0.3.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`53eff6b`](https://github.com/bluesky-social/toolbox/commit/53eff6bff0a448d8ed2eca7cc5bf8d1985641991) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Rename insert() to replace() for active facet, add new public insert() method
14
+
3
15
  ## 0.3.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -115,11 +115,11 @@ activeFacet: {
115
115
  type: 'mention', // the facet type
116
116
  value: 'er', // content after trigger (no @)
117
117
  range: {start: 0, end: 3},
118
- insert: (value, options?) => void,
118
+ replace: (value, options?) => void,
119
119
  }
120
120
  ```
121
121
 
122
- Call `activeFacet.insert('@eric')` to replace the in-progress facet with a
122
+ Call `activeFacet.replace('@eric')` to replace the in-progress facet with a
123
123
  final value. A trailing space is appended by default; pass
124
124
  `{noTrailingSpace: true}` to suppress it.
125
125
 
@@ -147,7 +147,7 @@ useEffect(() => {
147
147
  const name = facet.value.slice(0, -1) // strip closing ":"
148
148
  const emoji = EMOJI[name]
149
149
  if (emoji) {
150
- facet.insert(emoji, {noTrailingSpace: true})
150
+ facet.replace(emoji, {noTrailingSpace: true})
151
151
  }
152
152
  }
153
153
  })
@@ -168,11 +168,11 @@ in a `useEffect` cleanup.
168
168
  | ---------------- | --------------------------- | ----------------------------------------------- |
169
169
  | `activeFacet` | `TapperActiveFacet \| null` | Cursor enters or leaves a facet |
170
170
  | `facetCommitted` | `TapperFacet` | A facet is finalized (cursor left or committed) |
171
- | `afterInsert` | `TapperFacet` | After `insert()` replaces text |
171
+ | `afterInsert` | `TapperFacet` | After `replace()` replaces text |
172
172
 
173
173
  ### `focus()`
174
174
 
175
- After `insert()` replaces text, the input may lose focus on some platforms.
175
+ After `replace()` replaces text, the input may lose focus on some platforms.
176
176
  Listen for `afterInsert` and call `focus()` to restore it:
177
177
 
178
178
  ```tsx
package/build/index.d.ts CHANGED
@@ -31,11 +31,13 @@ export declare class Tapper {
31
31
  };
32
32
  }) => void;
33
33
  replaceText: (text: string, cursor?: number) => void;
34
- private insert;
34
+ private replace;
35
+ insert: (text: string) => void;
35
36
  }
36
37
  export declare function useTapper(config: TapperConfig): {
37
38
  on: <K extends keyof TapperEvents>(event: K, cb: (data: TapperEvents[K]) => void) => () => void;
38
39
  focus: () => void | undefined;
40
+ insert: (text: string) => void;
39
41
  inputProps: {
40
42
  ref: React.RefObject<any>;
41
43
  value: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAA;AAWhB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AAExB,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;IAwB5C,OAAO,CAAC,eAAe,CAAuD;IAC9E,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAM;IAEzC,OAAO,CAAC,QAAQ,CAAQ;IAGxB,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,MAAM,CAAC,EAAE,YAAY;IAejC,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;IAmEd,gBAAgB,GAAI,SAAS,MAAM,UA4ClC;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,UA4BA;IAED,WAAW,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,UA+B3C;IAED,OAAO,CAAC,MAAM,CAyBb;CACF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY;SAvOtC,CAAC,SAAS,MAAM,YAAY,2CAED,IAAI;;;aA+OhB,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC;;;gCArJZ,MAAM;+BA8CP;YAC1B,WAAW,EAAE;gBAAC,SAAS,EAAE;oBAAC,KAAK,EAAE,MAAM,CAAC;oBAAC,GAAG,EAAE,MAAM,CAAA;iBAAC,CAAA;aAAC,CAAA;SACvD;;;;;;EA4GF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,eAAe,EACf,cAAc,EACf,MAAM,SAAS,CAAA;AAWhB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AAExB,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;IAwB5C,OAAO,CAAC,eAAe,CAAuD;IAC9E,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAM;IAEzC,OAAO,CAAC,QAAQ,CAAQ;IAGxB,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,MAAM,CAAC,EAAE,YAAY;IAejC,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;IAmEd,gBAAgB,GAAI,SAAS,MAAM,UA4ClC;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,UA4BA;IAED,WAAW,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,UA+B3C;IAED,OAAO,CAAC,OAAO,CAyBd;IAED,MAAM,GAAI,MAAM,MAAM,UASrB;CACF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY;SAlPtC,CAAC,SAAS,MAAM,YAAY,2CAED,IAAI;;mBAoOrB,MAAM;;aAuBD,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC;;;gCAjKZ,MAAM;+BA8CP;YAC1B,WAAW,EAAE;gBAAC,SAAS,EAAE;oBAAC,KAAK,EAAE,MAAM,CAAC;oBAAC,GAAG,EAAE,MAAM,CAAA;iBAAC,CAAA;aAAC,CAAA;SACvD;;;;;;EAwHF"}
package/build/index.js CHANGED
@@ -113,7 +113,7 @@ export class Tapper {
113
113
  }
114
114
  // Build activeFacet with insert() baked in
115
115
  const active = detected
116
- ? { ...detected, insert: this.insert }
116
+ ? { ...detected, replace: this.replace }
117
117
  : null;
118
118
  this.text = newText;
119
119
  this.selection = selection;
@@ -213,7 +213,7 @@ export class Tapper {
213
213
  const pos = cursor ?? text.length;
214
214
  const detected = detectActiveFacet(nodes, text, pos, this.triggers);
215
215
  const active = detected
216
- ? { ...detected, insert: this.insert }
216
+ ? { ...detected, replace: this.replace }
217
217
  : null;
218
218
  this.text = text;
219
219
  this.selection = { start: pos, end: pos };
@@ -227,7 +227,7 @@ export class Tapper {
227
227
  this.emit('facetCommitted', nodeToFacet(node));
228
228
  }
229
229
  };
230
- insert = (value, options) => {
230
+ replace = (value, options) => {
231
231
  if (!this.activeFacet)
232
232
  return;
233
233
  const replacement = value + (options?.noTrailingSpace ? '' : ' ');
@@ -250,6 +250,16 @@ export class Tapper {
250
250
  };
251
251
  this.update(newText, { start: newEnd, end: newEnd });
252
252
  };
253
+ insert = (text) => {
254
+ const pos = this.selection.end;
255
+ const newText = this.text.slice(0, pos) + text + this.text.slice(pos);
256
+ const newEnd = pos + text.length;
257
+ this.pendingMutation = {
258
+ textLength: newText.length,
259
+ timestamp: Date.now(),
260
+ };
261
+ this.update(newText, { start: newEnd, end: newEnd });
262
+ };
253
263
  }
254
264
  export function useTapper(config) {
255
265
  const [store] = useState(() => new Tapper(config));
@@ -259,6 +269,7 @@ export function useTapper(config) {
259
269
  ...state,
260
270
  on: store.on,
261
271
  focus: () => inputRef.current?.focus(),
272
+ insert: store.insert,
262
273
  inputProps: {
263
274
  ref: inputRef,
264
275
  value: state.text,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {useRef, useState, useSyncExternalStore} from 'react'\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 * from './facets'\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 /*\n * Guard against stale native events after programmatic text replacement.\n *\n * After insert() or atomic deletion, we update this.text and this.selection\n * synchronously. However, iOS (especially on the old architecture bridge)\n * may then fire stale onChangeText and onSelectionChange events reflecting\n * the *previous* text/cursor state. These arrive within ~60ms.\n *\n * We record the expected textLength and a timestamp, then use two guards:\n *\n * 1. handleTextChange: if the incoming text length doesn't match the\n * expected length AND we're within the debounce window, it's a stale\n * event echoing the pre-replacement text. Skip it.\n *\n * 2. handleSelectionChange: if we're within the debounce window, skip\n * ALL selection events. The correct selection was already set by\n * update(), and any events within the window are either duplicates\n * (no-ops) or stale positions from the old text.\n *\n * After the window passes, the guard clears itself and normal event\n * handling resumes (cursor taps, typing, etc).\n */\n private pendingMutation: {textLength: number; timestamp: number} | null = null\n private static MUTATION_DEBOUNCE_MS = 100\n\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 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(newText: string, selection: TapperSelection) {\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 = isRange\n ? null\n : detectActiveFacet(nodes, newText, cursor, this.triggers)\n\n // When cursor leaves a facet, commit it\n let committedNode: TapperNode | null = null\n 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 committedNode = node\n break\n }\n }\n }\n\n // Build activeFacet with insert() baked in\n const active: TapperActiveFacet | null = detected\n ? {...detected, insert: this.insert}\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 if (committedNode) {\n this.emit('facetCommitted', nodeToFacet(committedNode))\n }\n }\n\n handleTextChange = (newText: string) => {\n if (newText === this.text) return\n\n /*\n * Guard 1: stale text. After insert/atomic deletion, iOS may echo\n * onChangeText with the pre-replacement text. If the incoming length\n * doesn't match what we expect and we're within the debounce window,\n * this is a stale echo — skip it.\n */\n if (this.pendingMutation !== null) {\n const elapsed = Date.now() - this.pendingMutation.timestamp\n if (\n elapsed < Tapper.MUTATION_DEBOUNCE_MS &&\n newText.length !== this.pendingMutation.textLength\n ) {\n return\n }\n }\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 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 this.pendingMutation = {\n textLength: newText.length,\n timestamp: Date.now(),\n }\n break\n }\n }\n }\n\n this.update(newText, {start: newEnd, end: newEnd})\n }\n\n handleSelectionChange = (e: {\n nativeEvent: {selection: {start: number; end: number}}\n }) => {\n const {start, end} = e.nativeEvent.selection\n\n /*\n * Guard 2: stale selection. After insert/atomic deletion, iOS may fire\n * onSelectionChange with cursor positions from the old text state\n * (e.g. a \"deselection\" event at the pre-insert cursor). Within the\n * debounce window, skip ALL selection events — the correct selection\n * was already set synchronously by update().\n */\n if (this.pendingMutation !== null) {\n const elapsed = Date.now() - this.pendingMutation.timestamp\n if (elapsed < Tapper.MUTATION_DEBOUNCE_MS) return\n this.pendingMutation = null\n }\n\n /*\n * Guard 3: premature selection. iOS sometimes fires onSelectionChange\n * before onChangeText for the same edit, reporting a cursor position\n * that's beyond the current text length. Skip it — handleTextChange\n * will set the correct position when it arrives.\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 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, insert: this.insert}\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 insert = (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.pendingMutation = {\n textLength: newText.length,\n timestamp: Date.now(),\n }\n this.update(newText, {start: newEnd, end: 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 inputRef = useRef<{focus(): void}>(null)\n\n return {\n ...state,\n on: store.on,\n focus: () => inputRef.current?.focus(),\n inputProps: {\n ref: inputRef as React.RefObject<any>,\n value: state.text,\n selection: state.selection,\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 {useRef, useState, useSyncExternalStore} from 'react'\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 * from './facets'\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 /*\n * Guard against stale native events after programmatic text replacement.\n *\n * After insert() or atomic deletion, we update this.text and this.selection\n * synchronously. However, iOS (especially on the old architecture bridge)\n * may then fire stale onChangeText and onSelectionChange events reflecting\n * the *previous* text/cursor state. These arrive within ~60ms.\n *\n * We record the expected textLength and a timestamp, then use two guards:\n *\n * 1. handleTextChange: if the incoming text length doesn't match the\n * expected length AND we're within the debounce window, it's a stale\n * event echoing the pre-replacement text. Skip it.\n *\n * 2. handleSelectionChange: if we're within the debounce window, skip\n * ALL selection events. The correct selection was already set by\n * update(), and any events within the window are either duplicates\n * (no-ops) or stale positions from the old text.\n *\n * After the window passes, the guard clears itself and normal event\n * handling resumes (cursor taps, typing, etc).\n */\n private pendingMutation: {textLength: number; timestamp: number} | null = null\n private static MUTATION_DEBOUNCE_MS = 100\n\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 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(newText: string, selection: TapperSelection) {\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 = isRange\n ? null\n : detectActiveFacet(nodes, newText, cursor, this.triggers)\n\n // When cursor leaves a facet, commit it\n let committedNode: TapperNode | null = null\n 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 committedNode = 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 if (committedNode) {\n this.emit('facetCommitted', nodeToFacet(committedNode))\n }\n }\n\n handleTextChange = (newText: string) => {\n if (newText === this.text) return\n\n /*\n * Guard 1: stale text. After insert/atomic deletion, iOS may echo\n * onChangeText with the pre-replacement text. If the incoming length\n * doesn't match what we expect and we're within the debounce window,\n * this is a stale echo — skip it.\n */\n if (this.pendingMutation !== null) {\n const elapsed = Date.now() - this.pendingMutation.timestamp\n if (\n elapsed < Tapper.MUTATION_DEBOUNCE_MS &&\n newText.length !== this.pendingMutation.textLength\n ) {\n return\n }\n }\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 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 this.pendingMutation = {\n textLength: newText.length,\n timestamp: Date.now(),\n }\n break\n }\n }\n }\n\n this.update(newText, {start: newEnd, end: newEnd})\n }\n\n handleSelectionChange = (e: {\n nativeEvent: {selection: {start: number; end: number}}\n }) => {\n const {start, end} = e.nativeEvent.selection\n\n /*\n * Guard 2: stale selection. After insert/atomic deletion, iOS may fire\n * onSelectionChange with cursor positions from the old text state\n * (e.g. a \"deselection\" event at the pre-insert cursor). Within the\n * debounce window, skip ALL selection events — the correct selection\n * was already set synchronously by update().\n */\n if (this.pendingMutation !== null) {\n const elapsed = Date.now() - this.pendingMutation.timestamp\n if (elapsed < Tapper.MUTATION_DEBOUNCE_MS) return\n this.pendingMutation = null\n }\n\n /*\n * Guard 3: premature selection. iOS sometimes fires onSelectionChange\n * before onChangeText for the same edit, reporting a cursor position\n * that's beyond the current text length. Skip it — handleTextChange\n * will set the correct position when it arrives.\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 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.pendingMutation = {\n textLength: newText.length,\n timestamp: Date.now(),\n }\n this.update(newText, {start: newEnd, end: 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.pendingMutation = {\n textLength: newText.length,\n timestamp: Date.now(),\n }\n this.update(newText, {start: newEnd, end: 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 inputRef = useRef<{focus(): void}>(null)\n\n return {\n ...state,\n on: store.on,\n focus: () => inputRef.current?.focus(),\n insert: store.insert,\n inputProps: {\n ref: inputRef as React.RefObject<any>,\n value: state.text,\n selection: state.selection,\n onChangeText: store.handleTextChange,\n onSelectionChange: store.handleSelectionChange,\n },\n }\n}\n"]}
package/build/types.d.ts CHANGED
@@ -34,7 +34,7 @@ export type TapperFacet = {
34
34
  };
35
35
  };
36
36
  export type TapperActiveFacet = TapperFacet & {
37
- insert: (value: string, options?: {
37
+ replace: (value: string, options?: {
38
38
  noTrailingSpace?: boolean;
39
39
  }) => void;
40
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEtD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,GACD;IACE,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,GACD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAEL,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,IAAI,CAAA;CACvE,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAAA;AAE1D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,eAAe,CAAA;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACrC,cAAc,EAAE,WAAW,CAAA;IAC3B,WAAW,EAAE,WAAW,CAAA;CACzB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEtD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,GACD;IACE,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,GACD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAEL,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAC,eAAe,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,IAAI,CAAA;CACxE,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAAA;AAE1D,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,eAAe,CAAA;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACrC,cAAc,EAAE,WAAW,CAAA;IAC3B,WAAW,EAAE,WAAW,CAAA;CACzB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type TapperFacetConfig = Record<string, RegExp>\n\nexport type TapperNode =\n | {\n type: 'text'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType?: undefined\n }\n | {\n type: 'trigger'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType: string\n }\n | {\n type: 'facet'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType: string\n }\n\nexport type TapperFacet = {\n type: string\n raw: string\n value: string\n range: {start: number; end: number}\n}\n\nexport type TapperActiveFacet = TapperFacet & {\n insert: (value: string, options?: {noTrailingSpace?: boolean}) => void\n}\n\nexport type TapperSelection = {start: number; end: number}\n\nexport type TapperSnapshot = {\n text: string\n selection: TapperSelection\n nodes: TapperNode[]\n activeFacet: TapperActiveFacet | null\n}\n\nexport type TapperConfig = {\n facets?: TapperFacetConfig\n initialText?: string\n}\n\nexport type TapperEvents = {\n activeFacet: TapperActiveFacet | null\n facetCommitted: TapperFacet\n afterInsert: TapperFacet\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type TapperFacetConfig = Record<string, RegExp>\n\nexport type TapperNode =\n | {\n type: 'text'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType?: undefined\n }\n | {\n type: 'trigger'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType: string\n }\n | {\n type: 'facet'\n raw: string\n value: string\n start: number\n end: number\n committed?: boolean\n facetType: string\n }\n\nexport type TapperFacet = {\n type: string\n raw: string\n value: string\n range: {start: number; end: number}\n}\n\nexport type TapperActiveFacet = TapperFacet & {\n replace: (value: string, options?: {noTrailingSpace?: boolean}) => void\n}\n\nexport type TapperSelection = {start: number; end: number}\n\nexport type TapperSnapshot = {\n text: string\n selection: TapperSelection\n nodes: TapperNode[]\n activeFacet: TapperActiveFacet | null\n}\n\nexport type TapperConfig = {\n facets?: TapperFacetConfig\n initialText?: string\n}\n\nexport type TapperEvents = {\n activeFacet: TapperActiveFacet | null\n facetCommitted: TapperFacet\n afterInsert: TapperFacet\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsky.app/tapper",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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",
package/src/index.ts CHANGED
@@ -157,7 +157,7 @@ export class Tapper {
157
157
 
158
158
  // Build activeFacet with insert() baked in
159
159
  const active: TapperActiveFacet | null = detected
160
- ? {...detected, insert: this.insert}
160
+ ? {...detected, replace: this.replace}
161
161
  : null
162
162
 
163
163
  this.text = newText
@@ -275,7 +275,7 @@ export class Tapper {
275
275
  const pos = cursor ?? text.length
276
276
  const detected = detectActiveFacet(nodes, text, pos, this.triggers)
277
277
  const active: TapperActiveFacet | null = detected
278
- ? {...detected, insert: this.insert}
278
+ ? {...detected, replace: this.replace}
279
279
  : null
280
280
 
281
281
  this.text = text
@@ -293,7 +293,7 @@ export class Tapper {
293
293
  }
294
294
  }
295
295
 
296
- private insert = (value: string, options?: {noTrailingSpace?: boolean}) => {
296
+ private replace = (value: string, options?: {noTrailingSpace?: boolean}) => {
297
297
  if (!this.activeFacet) return
298
298
 
299
299
  const replacement = value + (options?.noTrailingSpace ? '' : ' ')
@@ -319,6 +319,17 @@ export class Tapper {
319
319
  }
320
320
  this.update(newText, {start: newEnd, end: newEnd})
321
321
  }
322
+
323
+ insert = (text: string) => {
324
+ const pos = this.selection.end
325
+ const newText = this.text.slice(0, pos) + text + this.text.slice(pos)
326
+ const newEnd = pos + text.length
327
+ this.pendingMutation = {
328
+ textLength: newText.length,
329
+ timestamp: Date.now(),
330
+ }
331
+ this.update(newText, {start: newEnd, end: newEnd})
332
+ }
322
333
  }
323
334
 
324
335
  export function useTapper(config: TapperConfig) {
@@ -330,6 +341,7 @@ export function useTapper(config: TapperConfig) {
330
341
  ...state,
331
342
  on: store.on,
332
343
  focus: () => inputRef.current?.focus(),
344
+ insert: store.insert,
333
345
  inputProps: {
334
346
  ref: inputRef as React.RefObject<any>,
335
347
  value: state.text,
package/src/types.ts CHANGED
@@ -37,7 +37,7 @@ export type TapperFacet = {
37
37
  }
38
38
 
39
39
  export type TapperActiveFacet = TapperFacet & {
40
- insert: (value: string, options?: {noTrailingSpace?: boolean}) => void
40
+ replace: (value: string, options?: {noTrailingSpace?: boolean}) => void
41
41
  }
42
42
 
43
43
  export type TapperSelection = {start: number; end: number}