dictate-button 1.4.0 → 1.5.0

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Dictate Button (Web Component)
2
2
  ![NPM Version](https://img.shields.io/npm/v/dictate-button)
3
3
 
4
- A customizable web component that adds speech-to-text dictation capabilities to any text input or textarea field on your website.
4
+ A customizable web component that adds speech-to-text dictation capabilities to any text input, textarea field, or contenteditable element on your website.
5
5
 
6
6
  Developed for [dictate-button.io](https://dictate-button.io).
7
7
 
@@ -9,7 +9,7 @@ Developed for [dictate-button.io](https://dictate-button.io).
9
9
 
10
10
  - Easy integration with any website
11
11
  - Compatible with any framework (or no framework)
12
- - Automatic injection into any textarea or text input with the `data-dictate-button-on` attribute (exclusive mode) or without the `data-dictate-button-off` attribute (inclusive mode)
12
+ - Automatic injection into text fields with the `data-dictate-button-on` attribute (exclusive mode) or without the `data-dictate-button-off` attribute (inclusive mode)
13
13
  - Simple speech-to-text functionality with clean UI
14
14
  - Customizable size and API endpoint
15
15
  - Dark and light theme support
@@ -23,6 +23,7 @@ Developed for [dictate-button.io](https://dictate-button.io).
23
23
  - input[type="text"]
24
24
  - input[type="search"]
25
25
  - input (without a type; defaults to text)
26
+ - [contenteditable] elements
26
27
 
27
28
  ## Usage
28
29
 
@@ -39,6 +40,7 @@ Both auto-inject modes:
39
40
  - Automatically run on DOMContentLoaded (or immediately if the DOM is already loaded).
40
41
  - Watch for DOM changes to apply the dictate button to newly added elements.
41
42
  - Set the button’s language from `document.documentElement.lang` (if present). Long codes like `en-GB` are normalized to `en`.
43
+ - Position the button to the top right-hand corner of the text field, respecting its padding with 4px fallback if the padding is not set (0).
42
44
 
43
45
  ### From CDN
44
46
 
@@ -51,13 +53,14 @@ In your HTML `<head>` tag, add the following script tags:
51
53
  <script type="module" crossorigin src="https://cdn.dictate-button.io/inject-exclusive.js"></script>
52
54
  ```
53
55
 
54
- Add the `data-dictate-button-on` attribute to any `textarea`, `input[type="text"]`, `input[type="search"]`, or `input` without a `type` attribute:
56
+ Add the `data-dictate-button-on` attribute to any `textarea`, `input[type="text"]`, `input[type="search"]`, `input` without a `type` attribute, or element with the `contenteditable` attribute:
55
57
 
56
58
  ```html
57
59
  <textarea data-dictate-button-on></textarea>
58
60
  <input type="text" data-dictate-button-on />
59
61
  <input type="search" data-dictate-button-on />
60
62
  <input data-dictate-button-on />
63
+ <div contenteditable data-dictate-button-on />
61
64
  ```
62
65
 
63
66
  #### Option 2: Using the inclusive auto-inject script
@@ -69,7 +72,7 @@ In your HTML `<head>` tag, add the following script tags:
69
72
  <script type="module" crossorigin src="https://cdn.dictate-button.io/inject-inclusive.js"></script>
70
73
  ```
71
74
 
72
- All `textarea`, `input[type="text"]`, `input[type="search"]`, and `input` elements without a `type` attribute that lack `data-dictate-button-off` will be automatically enhanced by default.
75
+ All `textarea`, `input[type="text"]`, `input[type="search"]`, `input` elements without a `type` attribute, and elements with the `contenteditable` attribute that lack `data-dictate-button-off` will be automatically enhanced by default.
73
76
 
74
77
  To disable that for a specific field, add the `data-dictate-button-off` attribute to it this way:
75
78
 
@@ -78,6 +81,7 @@ To disable that for a specific field, add the `data-dictate-button-off` attribut
78
81
  <input type="text" data-dictate-button-off />
79
82
  <input type="search" data-dictate-button-off />
80
83
  <input data-dictate-button-off />
84
+ <div contenteditable data-dictate-button-off />
81
85
  ```
82
86
 
83
87
  #### Option 3: Manual integration
@@ -136,9 +140,9 @@ injectDictateButtonOnLoad(
136
140
  'input.custom-selector', // CSS selector for target elements
137
141
  {
138
142
  buttonSize: 30, // Button size in pixels (optional; default: 30)
139
- watchDomChanges: true, // Watch for DOM changes (optional; default: false)
140
143
  verbose: false, // Log events to console (optional; default: false)
141
- customApiEndpoint: 'https://api.example.com/transcribe' // Optional custom API endpoint
144
+ customApiEndpoint: 'https://api.example.com/transcribe', // Optional custom API endpoint
145
+ watchDomChanges: true // Watch for DOM changes (optional; default: false)
142
146
  }
143
147
  )
144
148
  ```
@@ -8,20 +8,20 @@ const S = 1, R = 2, lt = {
8
8
  context: null,
9
9
  owner: null
10
10
  };
11
- var _ = null;
11
+ var y = null;
12
12
  let V = null, Ct = null, p = null, g = null, m = null, L = 0;
13
13
  function mt(t, e) {
14
- const n = p, o = _, r = t.length === 0, i = e === void 0 ? o : e, l = r ? lt : {
14
+ const n = p, o = y, r = t.length === 0, i = e === void 0 ? o : e, l = r ? lt : {
15
15
  owned: null,
16
16
  cleanups: null,
17
17
  context: i ? i.context : null,
18
18
  owner: i
19
19
  }, s = r ? t : () => t(() => W(() => $(l)));
20
- _ = l, p = null;
20
+ y = l, p = null;
21
21
  try {
22
22
  return T(s, !0);
23
23
  } finally {
24
- p = n, _ = o;
24
+ p = n, y = o;
25
25
  }
26
26
  }
27
27
  function at(t, e) {
@@ -85,14 +85,14 @@ function K(t) {
85
85
  }
86
86
  function xt(t, e, n) {
87
87
  let o;
88
- const r = _, i = p;
89
- p = _ = t;
88
+ const r = y, i = p;
89
+ p = y = t;
90
90
  try {
91
91
  o = t.fn(e);
92
92
  } catch (l) {
93
93
  return t.pure && (t.state = S, t.owned && t.owned.forEach($), t.owned = null), t.updatedAt = n + 1, gt(l);
94
94
  } finally {
95
- p = i, _ = r;
95
+ p = i, y = r;
96
96
  }
97
97
  (!t.updatedAt || t.updatedAt <= n) && (t.updatedAt != null && "observers" in t ? ut(t, o) : t.value = o, t.updatedAt = n);
98
98
  }
@@ -106,11 +106,11 @@ function dt(t, e, n, o = S, r) {
106
106
  sourceSlots: null,
107
107
  cleanups: null,
108
108
  value: e,
109
- owner: _,
110
- context: _ ? _.context : null,
109
+ owner: y,
110
+ context: y ? y.context : null,
111
111
  pure: n
112
112
  };
113
- return _ === null || _ !== lt && (_.owned ? _.owned.push(i) : _.owned = [i]), i;
113
+ return y === null || y !== lt && (y.owned ? y.owned.push(i) : y.owned = [i]), i;
114
114
  }
115
115
  function ft(t) {
116
116
  if (t.state === 0) return;
@@ -191,7 +191,7 @@ function Et(t) {
191
191
  cause: t
192
192
  });
193
193
  }
194
- function gt(t, e = _) {
194
+ function gt(t, e = y) {
195
195
  throw Et(t);
196
196
  }
197
197
  function N(t, e) {
@@ -387,13 +387,13 @@ function x(t, e, n, o) {
387
387
  function Pt(t) {
388
388
  return Object.keys(t).reduce((n, o) => {
389
389
  const r = t[o];
390
- return n[o] = Object.assign({}, r), _t(r.value) && !Rt(r.value) && !Array.isArray(r.value) && (n[o].value = Object.assign({}, r.value)), Array.isArray(r.value) && (n[o].value = r.value.slice(0)), n;
390
+ return n[o] = Object.assign({}, r), yt(r.value) && !Rt(r.value) && !Array.isArray(r.value) && (n[o].value = Object.assign({}, r.value)), Array.isArray(r.value) && (n[o].value = r.value.slice(0)), n;
391
391
  }, {});
392
392
  }
393
393
  function Mt(t) {
394
394
  return t ? Object.keys(t).reduce((n, o) => {
395
395
  const r = t[o];
396
- return n[o] = _t(r) && "value" in r ? r : {
396
+ return n[o] = yt(r) && "value" in r ? r : {
397
397
  value: r
398
398
  }, n[o].attribute || (n[o].attribute = Bt(o)), n[o].parse = "parse" in n[o] ? n[o].parse : typeof n[o].value != "string", n;
399
399
  }, {}) : {};
@@ -436,7 +436,7 @@ function rt(t, e, n, o) {
436
436
  function Bt(t) {
437
437
  return t.replace(/\.?([A-Z]+)/g, (e, n) => "-" + n.toLowerCase()).replace("_", "-").replace(/^-/, "");
438
438
  }
439
- function _t(t) {
439
+ function yt(t) {
440
440
  return t != null && (typeof t == "object" || typeof t == "function");
441
441
  }
442
442
  function Rt(t) {
@@ -454,6 +454,8 @@ function Dt(t, e) {
454
454
  }
455
455
  constructor() {
456
456
  super(), this.__initialized = !1, this.__released = !1, this.__releaseCallbacks = [], this.__propertyChangedCallbacks = [], this.__updating = {}, this.props = {};
457
+ for (let r of n)
458
+ this[r] = void 0;
457
459
  }
458
460
  connectedCallback() {
459
461
  if (this.__initialized) return;
@@ -605,7 +607,7 @@ const Vt = `
605
607
  }
606
608
  `;
607
609
  var qt = /* @__PURE__ */ O('<div part=container class=dictate-button__container><style></style><div aria-live=polite class=dictate-button__status-announcer style="position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0"></div><button part=button class=dictate-button__button>'), Ht = /* @__PURE__ */ O('<svg part=icon class="dictate-button__icon dictate-button__icon--idle"fill=none viewBox="0 0 24 24"stroke-width=1.5 stroke=currentColor role=img aria-hidden=true><path stroke-linecap=round stroke-linejoin=round d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z">'), Gt = /* @__PURE__ */ O('<svg part=icon class="dictate-button__icon dictate-button__icon--recording"viewBox="0 0 24 24"fill=currentColor role=img aria-hidden=true><circle cx=12 cy=12 r=10>'), Wt = /* @__PURE__ */ O('<svg part=icon class="dictate-button__icon dictate-button__icon--processing"viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=1.5 stroke-linecap=round stroke-linejoin=round role=img aria-hidden=true><path d="M12 2v4"></path><path d="m16.2 7.8 2.9-2.9"></path><path d="M18 12h4"></path><path d="m16.2 16.2 2.9 2.9"></path><path d="M12 18v4"></path><path d="m4.9 19.1 2.9-2.9"></path><path d="M2 12h4"></path><path d="m4.9 4.9 2.9 2.9">'), Xt = /* @__PURE__ */ O('<svg part=icon class="dictate-button__icon dictate-button__icon--error"viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=4 stroke-linecap=round stroke-linejoin=round role=img aria-hidden=true><line x1=12 x2=12 y1=4 y2=14></line><line x1=12 x2=12.01 y1=20 y2=20>');
608
- console.debug("dictate-button version:", "1.4.0");
610
+ console.debug("dictate-button version:", "1.5.0");
609
611
  const Jt = "https://api.dictate-button.io/transcribe", v = "dictate-button.io", H = -70, ot = -10, it = 0, Zt = 4, Qt = 0.25, Yt = 0.05;
610
612
  zt("dictate-button", {
611
613
  size: 30,
@@ -618,36 +620,36 @@ zt("dictate-button", {
618
620
  const [n, o] = at("idle");
619
621
  let r = null, i = [], l = null, s = null, a = null, u = !1, d = 0;
620
622
  const w = (f) => f <= H ? 0 : f >= ot ? 1 : (f - H) / (ot - H), E = (f) => {
621
- let y = 0;
623
+ let _ = 0;
622
624
  for (let b = 0; b < f.length; b++) {
623
625
  const c = (f[b] - 128) / 128;
624
- y += c * c;
626
+ _ += c * c;
625
627
  }
626
- return Math.sqrt(y / f.length);
628
+ return Math.sqrt(_ / f.length);
627
629
  }, P = (f) => 20 * Math.log10(Math.max(f, 1e-8)), M = (f) => {
628
- const y = e.shadowRoot.querySelector(".dictate-button__button");
629
- if (!y)
630
+ const _ = e.shadowRoot.querySelector(".dictate-button__button");
631
+ if (!_)
630
632
  return;
631
633
  const b = it + f * (Zt - it), c = 0 + f * 0.4;
632
- y.style.boxShadow = `0 0 0 ${b}px light-dark(rgba(0, 0, 0, ${c}), rgba(255, 255, 255, ${c}))`;
634
+ _.style.boxShadow = `0 0 0 ${b}px light-dark(rgba(0, 0, 0, ${c}), rgba(255, 255, 255, ${c}))`;
633
635
  }, X = () => {
634
636
  if (!u || !s || !a) return;
635
637
  s.getByteTimeDomainData(a);
636
- const f = E(a), y = P(f), b = w(y), c = b > d ? Qt : Yt;
638
+ const f = E(a), _ = P(f), b = w(_), c = b > d ? Qt : Yt;
637
639
  d = c * b + (1 - c) * d, M(d), requestAnimationFrame(X);
638
640
  }, U = () => {
639
641
  r && r.state !== "inactive" && r.stop(), i = [], u = !1, l && l.state !== "closed" && l.close(), l = null, s = null, a = null, d = 0, M(0);
640
642
  };
641
643
  e.addEventListener("disconnected", U);
642
- const yt = async () => {
644
+ const _t = async () => {
643
645
  if (U(), n() === "idle")
644
646
  try {
645
647
  const f = await navigator.mediaDevices.getUserMedia({
646
648
  audio: !0
647
649
  });
648
650
  l = new (window.AudioContext || window.webkitAudioContext)();
649
- const y = l.createMediaStreamSource(f);
650
- s = l.createAnalyser(), s.fftSize = 2048, y.connect(s), a = new Uint8Array(s.fftSize), r = new MediaRecorder(f, {
651
+ const _ = l.createMediaStreamSource(f);
652
+ s = l.createAnalyser(), s.fftSize = 2048, _.connect(s), a = new Uint8Array(s.fftSize), r = new MediaRecorder(f, {
651
653
  mimeType: "audio/webm"
652
654
  }), i = [], r.ondataavailable = (b) => {
653
655
  i.push(b.data);
@@ -680,8 +682,8 @@ zt("dictate-button", {
680
682
  o("error"), setTimeout(() => o("idle"), 2e3);
681
683
  };
682
684
  return (() => {
683
- var f = qt(), y = f.firstChild, b = y.nextSibling, c = b.nextSibling;
684
- return C(y, Vt), C(b, () => st(n())), c.$$click = yt, C(c, (() => {
685
+ var f = qt(), _ = f.firstChild, b = _.nextSibling, c = b.nextSibling;
686
+ return C(_, Vt), C(b, () => st(n())), c.$$click = _t, C(c, (() => {
685
687
  var h = j(() => n() === "idle");
686
688
  return () => h() && N(ee, {});
687
689
  })(), null), C(c, (() => {
@@ -3,7 +3,8 @@ const a = 30, n = !0, e = !1, o = [
3
3
  "textarea[data-dictate-button-on]:not([data-dictate-button-enabled])",
4
4
  'input[type="text"][data-dictate-button-on]:not([data-dictate-button-enabled])',
5
5
  'input[type="search"][data-dictate-button-on]:not([data-dictate-button-enabled])',
6
- "input[data-dictate-button-on]:not([type]):not([data-dictate-button-enabled])"
6
+ "input[data-dictate-button-on]:not([type]):not([data-dictate-button-enabled])",
7
+ "*[contenteditable][data-dictate-button-on]:not([data-dictate-button-enabled])"
7
8
  ].join(",");
8
9
  t(o, {
9
10
  buttonSize: a,
@@ -3,7 +3,8 @@ const a = 30, n = !0, o = !1, e = [
3
3
  "textarea:not([data-dictate-button-off]):not([data-dictate-button-enabled])",
4
4
  'input[type="text"]:not([data-dictate-button-off]):not([data-dictate-button-enabled])',
5
5
  'input[type="search"]:not([data-dictate-button-off]):not([data-dictate-button-enabled])',
6
- "input:not([type]):not([data-dictate-button-off]):not([data-dictate-button-enabled])"
6
+ "input:not([type]):not([data-dictate-button-off]):not([data-dictate-button-enabled])",
7
+ "*[contenteditable]:not([data-dictate-button-off]):not([data-dictate-button-enabled])"
7
8
  ].join(",");
8
9
  t(e, {
9
10
  buttonSize: a,
@@ -1,39 +1,40 @@
1
- function T(t, c = {}) {
2
- const { buttonSize: i = 30, verbose: o = !1, customApiEndpoint: r } = c, d = document.querySelectorAll(t);
3
- for (const n of d) {
4
- if (n.hasAttribute("data-dictate-button-enabled")) continue;
5
- const u = n.parentNode;
6
- if (!n.isConnected || !u) {
7
- o && console.debug("injectDictateButton: skipping detached field", n);
1
+ function v(t, c = {}) {
2
+ const { buttonSize: n = 30, verbose: i = !1, customApiEndpoint: a } = c, l = document.querySelectorAll(t);
3
+ for (const e of l) {
4
+ if (e.hasAttribute("data-dictate-button-enabled")) continue;
5
+ const p = e.parentNode;
6
+ if (!e.isConnected || !p) {
7
+ i && console.debug("injectDictateButton: skipping detached field", e);
8
8
  continue;
9
9
  }
10
- n.setAttribute("data-dictate-button-enabled", "");
11
- const s = document.createElement("div");
12
- s.style.position = "relative";
13
- const l = getComputedStyle(n), g = l.display === "block";
14
- s.style.display = g ? "block" : "inline-block", s.style.width = g ? "100%" : "auto", s.style.color = "inherit", s.classList.add("dictate-button-wrapper"), u.insertBefore(s, n), s.appendChild(n), s.style.margin = l.margin, n.style.margin = "0", n.style.boxSizing = "border-box";
15
- const p = parseFloat(l.paddingRight || "0") || 4;
16
- n.style.paddingRight = `${i + p * 2}px`;
17
- const e = document.createElement("dictate-button");
18
- e.size = i, e.style.position = "absolute", e.style.right = "0", e.style.top = m(
19
- s,
20
- n,
21
- i
22
- ), e.style.marginRight = e.style.marginLeft = `${p}px`, e.style.marginTop = "0", e.style.marginBottom = "0", r && (e.apiEndpoint = r), e.language = h(), e.addEventListener("recording:started", (a) => {
23
- o && console.debug("recording:started", a);
24
- }), e.addEventListener("recording:stopped", (a) => {
25
- o && console.debug("recording:stopped", a);
26
- }), e.addEventListener("recording:failed", (a) => {
27
- o && console.debug("recording:failed", a), b(n);
28
- }), e.addEventListener("transcribing:started", (a) => {
29
- o && console.debug("transcribing:started", a);
30
- }), e.addEventListener("transcribing:finished", (a) => {
31
- o && console.debug("transcribing:finished", a);
32
- const f = a.detail;
33
- y(n, f);
34
- }), e.addEventListener("transcribing:failed", (a) => {
35
- o && console.debug("transcribing:failed", a), b(n);
36
- }), s.appendChild(e);
10
+ e.setAttribute("data-dictate-button-enabled", "");
11
+ const r = document.createElement("div");
12
+ r.style.position = "relative";
13
+ const g = getComputedStyle(e), s = g.display === "block";
14
+ r.style.display = s ? "block" : "inline-block", r.style.width = s ? "100%" : "auto", r.style.color = "inherit", r.classList.add("dictate-button-wrapper"), p.insertBefore(r, e), r.appendChild(e), r.style.margin = g.margin, e.style.margin = "0", e.style.boxSizing = "border-box";
15
+ const u = m(g);
16
+ e.style.paddingRight = `${n + u * 2}px`;
17
+ const o = document.createElement("dictate-button");
18
+ o.size = n, o.style.position = "absolute", o.style.right = "0", o.style.top = E(
19
+ r,
20
+ g,
21
+ e.tagName,
22
+ n
23
+ ) + "px", o.style.marginRight = o.style.marginLeft = `${u}px`, o.style.marginTop = "0", o.style.marginBottom = "0", a && (o.apiEndpoint = a), o.language = h(), o.addEventListener("recording:started", (d) => {
24
+ i && console.debug("recording:started", d);
25
+ }), o.addEventListener("recording:stopped", (d) => {
26
+ i && console.debug("recording:stopped", d);
27
+ }), o.addEventListener("recording:failed", (d) => {
28
+ i && console.debug("recording:failed", d), f(e);
29
+ }), o.addEventListener("transcribing:started", (d) => {
30
+ i && console.debug("transcribing:started", d);
31
+ }), o.addEventListener("transcribing:finished", (d) => {
32
+ i && console.debug("transcribing:finished", d);
33
+ const b = d.detail;
34
+ T(e, b);
35
+ }), o.addEventListener("transcribing:failed", (d) => {
36
+ i && console.debug("transcribing:failed", d), f(e);
37
+ }), r.appendChild(o);
37
38
  }
38
39
  }
39
40
  function h() {
@@ -45,37 +46,95 @@ function h() {
45
46
  return t.split(/[-_]/)[0].toLowerCase();
46
47
  }
47
48
  }
48
- function m(t, c, i) {
49
- if (c.tagName.toLowerCase() === "textarea") {
50
- const r = getComputedStyle(c);
51
- return `${parseFloat(r.paddingTop || "0") || 4}px`;
49
+ function E(t, c, n, i) {
50
+ if (n.toLowerCase() === "textarea") {
51
+ const l = parseFloat(c.paddingTop || "0");
52
+ return Math.max(4, l);
52
53
  }
53
- const o = Math.round(t.clientHeight / 2 - i / 2);
54
- return `${Math.max(4, o)}px`;
54
+ const a = Math.round(t.clientHeight / 2 - i / 2);
55
+ return Math.max(4, a);
55
56
  }
56
- function y(t, c) {
57
- const i = typeof c == "string" ? c.trim() : String(c ?? "").trim();
58
- if (i.length === 0)
59
- return;
60
- const o = t.selectionStart ?? 0, r = t.selectionEnd ?? 0, d = o > 0 ? t.value.charAt(o - 1) : "", n = d && !/\s/.test(d), u = r < t.value.length ? t.value.charAt(r) : "", s = u && !/\s/.test(u), l = (n ? " " : "") + i + (s ? " " : ""), g = o + l.length, p = typeof t.scrollTop == "number" ? t.scrollTop : null;
57
+ function m(t) {
58
+ const c = parseFloat(t.paddingRight || "0");
59
+ return Math.max(c, 4);
60
+ }
61
+ function T(t, c) {
62
+ const n = typeof c == "string" ? c.trim() : String(c ?? "").trim();
63
+ n.length !== 0 && (y(t) ? N(t, n) : C(t, n), t.dispatchEvent(new Event("input", { bubbles: !0, composed: !0 })), f(t));
64
+ }
65
+ function f(t) {
66
+ try {
67
+ t.focus({ preventScroll: !0 });
68
+ } catch {
69
+ t.focus();
70
+ }
71
+ }
72
+ function y(t) {
73
+ return t.isContentEditable;
74
+ }
75
+ function C(t, c) {
76
+ const n = t.selectionStart ?? 0, i = t.selectionEnd ?? 0, a = n > 0 ? t.value.charAt(n - 1) : "", l = a && !/\s/.test(a), e = i < t.value.length ? t.value.charAt(i) : "", p = e && !/\s/.test(e), r = (l ? " " : "") + c + (p ? " " : ""), g = n + r.length, s = typeof t.scrollTop == "number" ? t.scrollTop : null;
61
77
  if (typeof t.setRangeText == "function")
62
- t.setRangeText(l, o, r, "end");
78
+ t.setRangeText(r, n, i, "end");
63
79
  else {
64
- t.value = t.value.substring(0, o) + l + t.value.substring(r);
80
+ t.value = t.value.substring(0, n) + r + t.value.substring(i);
65
81
  try {
66
82
  t.selectionStart = g, t.selectionEnd = g;
67
83
  } catch {
68
84
  }
69
85
  }
70
- p !== null && (t.scrollTop = p), t.dispatchEvent(new Event("input", { bubbles: !0, composed: !0 })), b(t);
86
+ s !== null && (t.scrollTop = s);
71
87
  }
72
- function b(t) {
73
- try {
74
- t.focus({ preventScroll: !0 });
75
- } catch {
76
- t.focus();
88
+ function N(t, c) {
89
+ const n = window.getSelection();
90
+ if (!(n && n.rangeCount > 0 && t.contains(n.getRangeAt(0).commonAncestorContainer))) {
91
+ f(t);
92
+ const l = document.createRange();
93
+ l.selectNodeContents(t), l.collapse(!1), n?.removeAllRanges(), n?.addRange(l);
94
+ }
95
+ const a = n?.getRangeAt(0);
96
+ if (a) {
97
+ const l = a.cloneRange(), e = a.cloneRange();
98
+ let p = !1;
99
+ l.collapse(!0);
100
+ try {
101
+ l.setStart(a.startContainer, 0);
102
+ const s = l.toString(), u = s.length > 0 ? s.charAt(s.length - 1) : "";
103
+ p = u !== "" && !/\s/.test(u);
104
+ } catch (s) {
105
+ console.debug(
106
+ "insertIntoContentEditable: Error checking text before cursor:",
107
+ s
108
+ );
109
+ }
110
+ let r = !1;
111
+ e.collapse(!1);
112
+ try {
113
+ if (e.endContainer.nodeType === Node.TEXT_NODE) {
114
+ const o = e.endContainer;
115
+ e.setEnd(o, o.length);
116
+ } else if (e.endContainer.nodeType === Node.ELEMENT_NODE) {
117
+ const o = e.endContainer;
118
+ o.childNodes.length > e.endOffset && e.setEnd(o, e.endOffset + 1);
119
+ }
120
+ const s = e.toString(), u = s.length > 0 ? s.charAt(0) : "";
121
+ r = u !== "" && !/\s/.test(u);
122
+ } catch (s) {
123
+ console.debug(
124
+ "insertIntoContentEditable: Error checking text after cursor:",
125
+ s
126
+ );
127
+ }
128
+ const g = (p ? " " : "") + c + (r ? " " : "");
129
+ try {
130
+ a.deleteContents();
131
+ const s = document.createTextNode(g);
132
+ a.insertNode(s), a.setStartAfter(s), a.setEndAfter(s), n?.removeAllRanges(), n?.addRange(a);
133
+ } catch (s) {
134
+ console.debug("insertIntoContentEditable: Error inserting text:", s), f(t), t.textContent = (t.textContent || "") + g;
135
+ }
77
136
  }
78
137
  }
79
138
  export {
80
- T as injectDictateButton
139
+ v as injectDictateButton
81
140
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dictate-button",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Dictate Button (Web Component)",
5
5
  "keywords": [
6
6
  "custom-element",
@@ -65,18 +65,18 @@
65
65
  "devDependencies": {
66
66
  "prettier": "^3.6.2",
67
67
  "typescript": "^5.9.2",
68
- "vite": "^7.1.5",
68
+ "vite": "^7.1.7",
69
69
  "vite-plugin-dts": "^4.5.4",
70
70
  "vite-plugin-solid": "^2.11.8",
71
71
  "vite-plugin-static-copy": "^3.1.2"
72
72
  },
73
- "homepage": "https://github.com/kkomelin/dictate-button",
73
+ "homepage": "https://github.com/dictate-button/dictate-button",
74
74
  "repository": {
75
75
  "type": "git",
76
- "url": "https://github.com/kkomelin/dictate-button.git"
76
+ "url": "https://github.com/dictate-button/dictate-button.git"
77
77
  },
78
78
  "bugs": {
79
- "url": "https://github.com/kkomelin/dictate-button/issues"
79
+ "url": "https://github.com/dictate-button/dictate-button/issues"
80
80
  },
81
81
  "scripts": {
82
82
  "build": "vite build",