svedit 0.11.0 → 0.11.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.
@@ -85,7 +85,6 @@ export class ToggleAnnotationCommand extends Command {
85
85
  /**
86
86
  * Command that adds a new line character at the current caret position.
87
87
  * Only works in text selections where newlines are allowed.
88
- * Disabled on mobile browsers where Shift+Enter has different behavior.
89
88
  */
90
89
  export class AddNewLineCommand extends Command {
91
90
  is_enabled(): any;
@@ -1 +1 @@
1
- {"version":3,"file":"Command.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Command.svelte.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAOC;;;;OAIG;IACH,qBAFW,GAAG,EAIb;IAbD;;;OAGG;IACH,kBAAwC;IAQvC,aAAsB;IAGvB;;;;;OAKG;IACH,cAFa,OAAO,CAInB;IAED;;;;;;OAMG;IACH,WAFa,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;;GAGG;AACH;IASC,gBAEC;CACD;AAED;;;GAGG;AACH;IACC,0CAGC;IADA,eAA0B;IAG3B,YAAoC;IAEpC,iBAEC;IAED,kBAqBC;IAED,gBAEC;CACD;AAED;;;;GAIG;AACH;IACC,kBAUC;IAED,gBAqBC;CACD;AAED;;;;GAIG;AACH;IAaC,gBAKC;CACD;AAED;;;GAGG;AACH;IACC,kBAEC;IAED,gBAwGC;CACD;AAED;;;GAGG;AACH;IAUC,gBAIC;CACD"}
1
+ {"version":3,"file":"Command.svelte.d.ts","sourceRoot":"","sources":["../src/lib/Command.svelte.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAOC;;;;OAIG;IACH,qBAFW,GAAG,EAIb;IAbD;;;OAGG;IACH,kBAAwC;IAQvC,aAAsB;IAGvB;;;;;OAKG;IACH,cAFa,OAAO,CAInB;IAED;;;;;;OAMG;IACH,WAFa,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;GAEG;AACH;IACC,kBAEC;IAED,gBAEC;CACD;AAED;;;GAGG;AACH;IASC,gBAEC;CACD;AAED;;;GAGG;AACH;IACC,0CAGC;IADA,eAA0B;IAG3B,YAAoC;IAEpC,iBAEC;IAED,kBAqBC;IAED,gBAEC;CACD;AAED;;;GAGG;AACH;IACC,kBASC;IAED,gBAqBC;CACD;AAED;;;;GAIG;AACH;IAaC,gBAKC;CACD;AAED;;;GAGG;AACH;IACC,kBAEC;IAED,gBAwGC;CACD;AAED;;;GAGG;AACH;IAUC,gBAIC;CACD"}
@@ -1,6 +1,6 @@
1
1
  import { insert_default_node, break_text_node } from './transforms.svelte.js';
2
2
  import { can_switch_annotation_type } from './doc_utils.js';
3
- import { is_selection_collapsed, is_mobile_browser, get_char_length, char_slice } from './utils.js';
3
+ import { is_selection_collapsed, get_char_length, char_slice } from './utils.js';
4
4
 
5
5
  /**
6
6
  * Base class for commands that can be executed in response to user actions
@@ -153,7 +153,6 @@ export class ToggleAnnotationCommand extends Command {
153
153
  /**
154
154
  * Command that adds a new line character at the current caret position.
155
155
  * Only works in text selections where newlines are allowed.
156
- * Disabled on mobile browsers where Shift+Enter has different behavior.
157
156
  */
158
157
  export class AddNewLineCommand extends Command {
159
158
  is_enabled() {
@@ -162,7 +161,6 @@ export class AddNewLineCommand extends Command {
162
161
 
163
162
  return (
164
163
  this.context.editable &&
165
- !is_mobile_browser() &&
166
164
  selection?.type === 'text' &&
167
165
  session.inspect(selection.path).allow_newlines
168
166
  );
@@ -1 +1 @@
1
- {"version":3,"file":"KeyMapper.svelte.d.ts","sourceRoot":"","sources":["../src/lib/KeyMapper.svelte.js"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,gDAgBC;AA6DD;;;;;;;;;;;GAWG;AACH;IAEE,mBAAqB;IACrB,wBAA2B;IAG5B;;OAEG;IACH,8BAGC;IAED;;OAEG;IACH,iBAIC;IAED;;OAEG;IACH,iCAUC;CACD"}
1
+ {"version":3,"file":"KeyMapper.svelte.d.ts","sourceRoot":"","sources":["../src/lib/KeyMapper.svelte.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,gDAgBC;AAuED;;;;;;;;;;;GAWG;AACH;IAEE,mBAAqB;IACrB,wBAA2B;IAG5B;;OAEG;IACH,8BAGC;IAED;;OAEG;IACH,iBAIC;IAED;;OAEG;IACH,iCAUC;CACD"}
@@ -1,3 +1,5 @@
1
+ import { is_virtual_keyboard_active } from './utils.js';
2
+
1
3
  const MODIFIER_KEYS = ['meta', 'ctrl', 'alt', 'shift'];
2
4
  const MODIFIER_EVENT_KEYS = {
3
5
  meta: 'metaKey',
@@ -34,7 +36,7 @@ export function define_keymap(keymap) {
34
36
  * Matches a keyboard event against a key combo string.
35
37
  * Example: 'meta+e,ctrl+e' matches either (metaKey && key==='e') OR (ctrlKey && key==='e')
36
38
  */
37
- function matches_key_combo(key_combo, event) {
39
+ function matches_key_combo(key_combo, event, virtual_keyboard_active = false) {
38
40
  const alternatives = key_combo.split(',');
39
41
 
40
42
  return alternatives.some((alternative) => {
@@ -42,14 +44,22 @@ function matches_key_combo(key_combo, event) {
42
44
  const modifiers = parts.filter((part) => MODIFIER_KEYS.includes(part));
43
45
  const non_modifier = parts.find((part) => !MODIFIER_KEYS.includes(part));
44
46
 
47
+ if (virtual_keyboard_active && modifiers.length > 0) {
48
+ return false;
49
+ }
50
+
45
51
  // Check if all specified modifiers are pressed
46
52
  const modifiers_match = modifiers.every((mod) => event[MODIFIER_EVENT_KEYS[mod]]);
47
53
 
48
54
  // Check if no unspecified modifiers are pressed
49
- const no_extra_modifiers = MODIFIER_KEYS.every((mod) => {
50
- if (modifiers.includes(mod)) return true; // This modifier is expected
51
- return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
52
- });
55
+ // NOTE: On virtual keyboards (iOS / Anroid) modifier keys are just ignored, because
56
+ // they must be assumed accidental (e.g. Shift activated as a result of auto-capitalization)
57
+ const no_extra_modifiers = virtual_keyboard_active
58
+ ? true
59
+ : MODIFIER_KEYS.every((mod) => {
60
+ if (modifiers.includes(mod)) return true; // This modifier is expected
61
+ return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
62
+ });
53
63
 
54
64
  // Check if the key matches
55
65
  const key_matches = event.key.toLowerCase() === non_modifier;
@@ -65,8 +75,10 @@ function matches_key_combo(key_combo, event) {
65
75
  * but don't crash the application.
66
76
  */
67
77
  function handle_key_map(key_map, event) {
78
+ const virtual_keyboard_active = is_virtual_keyboard_active();
79
+
68
80
  for (const [key_combo, commands] of Object.entries(key_map)) {
69
- if (matches_key_combo(key_combo, event)) {
81
+ if (matches_key_combo(key_combo, event, virtual_keyboard_active)) {
70
82
  // Find the first enabled command and execute it
71
83
  const enabled_command = commands.find((cmd) => cmd.is_enabled());
72
84
  if (enabled_command) {
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  /**
2
- * Detect if the current browser is on a mobile device
2
+ * Detect if the virtual keyboard is likely visible.
3
+ *
4
+ * This is a heuristic based on the visual viewport becoming smaller than the
5
+ * layout viewport.
6
+ *
7
+ * @returns {boolean} true if the virtual keyboard is likely active, false otherwise
8
+ */
9
+ export function is_virtual_keyboard_active(): boolean;
10
+ /**
11
+ * Detect if the current browser is likely on a mobile device.
12
+ *
13
+ * This uses the user agent only so touch-capable laptops are not treated as
14
+ * mobile browsers.
15
+ *
3
16
  * @returns {boolean} true if mobile browser, false otherwise
4
17
  */
5
18
  export function is_mobile_browser(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.js"],"names":[],"mappings":"AAMA;;;GAGG;AACH,qCAFa,OAAO,CAanB;AAGD;;;GAGG;AACH,6CAFa,OAAO,CAUnB;AAED;;;;;;;;;;;;;GAaG;AACH,qCARW,MAAM,GACJ,MAAM,CASlB;AAGD;;;;;;;;;;;;;;GAcG;AACH,iCATW,MAAM,SACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;;;;GAcG;AACH,gCATW,MAAM,SACN,MAAM,QACN,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;;;;;;;;;GAaG;AACH,0CARW,MAAM,gBACN,MAAM,GACJ,MAAM,CAmBlB;AAED;;;;;;;;;;;;;GAaG;AACH,0CARW,MAAM,eACN,MAAM,GACJ,MAAM,CAelB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,4DAZW,aAAa,eACb,MAAM,GACJ,CAAC,aAAa,EAAE,aAAa,CAAC,CA6C1C;AAED;;;;;;;;;;;;;GAaG;AACH,gDARW,aAAa,eACb,aAAa,GACX,aAAa,CA4CzB;AAED;;;;;;;;GAQG;AACH,qCANW,MAAM,GACJ,MAAM,CAUlB;AAMD;;;;;;;;;GASG;AACH,sDAHW,MAAM,GACJ,OAAO,CAQnB;AAED;;;;;;GAMG;AACH,oDAJW,MAAM,UACN,MAAM,GACJ,IAAI,CAQhB;AAED;;;;;;;;;;;;GAYG;AACH,qCANW,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GACpB,MAAM,CAmBlB;AAED;;;;;;;;GAQG;AACH,kDANW,MAAM,GACJ,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAuBlC;AAED;;;;;;GAMG;AACH,+BAJW,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KACtB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GACpB,OAAO,CAKnB;AAED,uEAgCC;AAED;;;;;GAKG;AACH,gDAHW,SAAS,GACP,cAAc,GAAG,IAAI,CAWjC;AAED,gEAMC;AApKD,6BAA8B,IAAI,CAAC;mCAtRsC,cAAc;+BAAd,cAAc;oCAAd,cAAc"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.js"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,8CAFa,OAAO,CAanB;AAED;;;;;;;GAOG;AACH,qCAFa,OAAO,CASnB;AAGD;;;GAGG;AACH,6CAFa,OAAO,CAUnB;AAED;;;;;;;;;;;;;GAaG;AACH,qCARW,MAAM,GACJ,MAAM,CASlB;AAGD;;;;;;;;;;;;;;GAcG;AACH,iCATW,MAAM,SACN,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;;;;GAcG;AACH,gCATW,MAAM,SACN,MAAM,QACN,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;;;;;;;;;GAaG;AACH,0CARW,MAAM,gBACN,MAAM,GACJ,MAAM,CAmBlB;AAED;;;;;;;;;;;;;GAaG;AACH,0CARW,MAAM,eACN,MAAM,GACJ,MAAM,CAelB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,4DAZW,aAAa,eACb,MAAM,GACJ,CAAC,aAAa,EAAE,aAAa,CAAC,CA6C1C;AAED;;;;;;;;;;;;;GAaG;AACH,gDARW,aAAa,eACb,aAAa,GACX,aAAa,CA4CzB;AAED;;;;;;;;GAQG;AACH,qCANW,MAAM,GACJ,MAAM,CAUlB;AAMD;;;;;;;;;GASG;AACH,sDAHW,MAAM,GACJ,OAAO,CAQnB;AAED;;;;;;GAMG;AACH,oDAJW,MAAM,UACN,MAAM,GACJ,IAAI,CAQhB;AAED;;;;;;;;;;;;GAYG;AACH,qCANW,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GACpB,MAAM,CAmBlB;AAED;;;;;;;;GAQG;AACH,kDANW,MAAM,GACJ,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAuBlC;AAED;;;;;;GAMG;AACH,+BAJW,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KACtB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GACpB,OAAO,CAKnB;AAED,uEAgCC;AAED;;;;;GAKG;AACH,gDAHW,SAAS,GACP,cAAc,GAAG,IAAI,CAWjC;AAED,gEAMC;AApKD,6BAA8B,IAAI,CAAC;mCA3SsC,cAAc;+BAAd,cAAc;oCAAd,cAAc"}
package/dist/utils.js CHANGED
@@ -5,20 +5,41 @@
5
5
  const SEGMENTER = new Intl.Segmenter('en', { granularity: 'grapheme' });
6
6
 
7
7
  /**
8
- * Detect if the current browser is on a mobile device
8
+ * Detect if the virtual keyboard is likely visible.
9
+ *
10
+ * This is a heuristic based on the visual viewport becoming smaller than the
11
+ * layout viewport.
12
+ *
13
+ * @returns {boolean} true if the virtual keyboard is likely active, false otherwise
14
+ */
15
+ export function is_virtual_keyboard_active() {
16
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
17
+ return false;
18
+ }
19
+
20
+ const visual_viewport = window.visualViewport;
21
+ if (!visual_viewport) {
22
+ return false;
23
+ }
24
+
25
+ return visual_viewport.height < document.documentElement.clientHeight;
26
+ }
27
+
28
+ /**
29
+ * Detect if the current browser is likely on a mobile device.
30
+ *
31
+ * This uses the user agent only so touch-capable laptops are not treated as
32
+ * mobile browsers.
33
+ *
9
34
  * @returns {boolean} true if mobile browser, false otherwise
10
35
  */
11
36
  export function is_mobile_browser() {
12
- if (typeof window === 'undefined' || typeof navigator === 'undefined') {
37
+ if (typeof navigator === 'undefined') {
13
38
  return false;
14
39
  }
15
40
 
16
41
  const user_agent = navigator.userAgent;
17
- return (
18
- /iPhone|iPad|iPod|Android|Mobile/i.test(user_agent) ||
19
- 'ontouchstart' in window ||
20
- navigator.maxTouchPoints > 0
21
- );
42
+ return /iPhone|iPad|iPod|Android|Mobile/i.test(user_agent);
22
43
  }
23
44
 
24
45
  // ‼️‼️‼️‼️‼️‼️ UNUSED UTILITY BELOW ‼️‼️‼️‼️‼️‼️
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svedit",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,6 +1,6 @@
1
1
  import { insert_default_node, break_text_node } from './transforms.svelte.js';
2
2
  import { can_switch_annotation_type } from './doc_utils.js';
3
- import { is_selection_collapsed, is_mobile_browser, get_char_length, char_slice } from './utils.js';
3
+ import { is_selection_collapsed, get_char_length, char_slice } from './utils.js';
4
4
 
5
5
  /**
6
6
  * Base class for commands that can be executed in response to user actions
@@ -153,7 +153,6 @@ export class ToggleAnnotationCommand extends Command {
153
153
  /**
154
154
  * Command that adds a new line character at the current caret position.
155
155
  * Only works in text selections where newlines are allowed.
156
- * Disabled on mobile browsers where Shift+Enter has different behavior.
157
156
  */
158
157
  export class AddNewLineCommand extends Command {
159
158
  is_enabled() {
@@ -162,7 +161,6 @@ export class AddNewLineCommand extends Command {
162
161
 
163
162
  return (
164
163
  this.context.editable &&
165
- !is_mobile_browser() &&
166
164
  selection?.type === 'text' &&
167
165
  session.inspect(selection.path).allow_newlines
168
166
  );
@@ -1,3 +1,5 @@
1
+ import { is_virtual_keyboard_active } from './utils.js';
2
+
1
3
  const MODIFIER_KEYS = ['meta', 'ctrl', 'alt', 'shift'];
2
4
  const MODIFIER_EVENT_KEYS = {
3
5
  meta: 'metaKey',
@@ -34,7 +36,7 @@ export function define_keymap(keymap) {
34
36
  * Matches a keyboard event against a key combo string.
35
37
  * Example: 'meta+e,ctrl+e' matches either (metaKey && key==='e') OR (ctrlKey && key==='e')
36
38
  */
37
- function matches_key_combo(key_combo, event) {
39
+ function matches_key_combo(key_combo, event, virtual_keyboard_active = false) {
38
40
  const alternatives = key_combo.split(',');
39
41
 
40
42
  return alternatives.some((alternative) => {
@@ -42,14 +44,22 @@ function matches_key_combo(key_combo, event) {
42
44
  const modifiers = parts.filter((part) => MODIFIER_KEYS.includes(part));
43
45
  const non_modifier = parts.find((part) => !MODIFIER_KEYS.includes(part));
44
46
 
47
+ if (virtual_keyboard_active && modifiers.length > 0) {
48
+ return false;
49
+ }
50
+
45
51
  // Check if all specified modifiers are pressed
46
52
  const modifiers_match = modifiers.every((mod) => event[MODIFIER_EVENT_KEYS[mod]]);
47
53
 
48
54
  // Check if no unspecified modifiers are pressed
49
- const no_extra_modifiers = MODIFIER_KEYS.every((mod) => {
50
- if (modifiers.includes(mod)) return true; // This modifier is expected
51
- return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
52
- });
55
+ // NOTE: On virtual keyboards (iOS / Anroid) modifier keys are just ignored, because
56
+ // they must be assumed accidental (e.g. Shift activated as a result of auto-capitalization)
57
+ const no_extra_modifiers = virtual_keyboard_active
58
+ ? true
59
+ : MODIFIER_KEYS.every((mod) => {
60
+ if (modifiers.includes(mod)) return true; // This modifier is expected
61
+ return !event[MODIFIER_EVENT_KEYS[mod]]; // This modifier should NOT be pressed
62
+ });
53
63
 
54
64
  // Check if the key matches
55
65
  const key_matches = event.key.toLowerCase() === non_modifier;
@@ -65,8 +75,10 @@ function matches_key_combo(key_combo, event) {
65
75
  * but don't crash the application.
66
76
  */
67
77
  function handle_key_map(key_map, event) {
78
+ const virtual_keyboard_active = is_virtual_keyboard_active();
79
+
68
80
  for (const [key_combo, commands] of Object.entries(key_map)) {
69
- if (matches_key_combo(key_combo, event)) {
81
+ if (matches_key_combo(key_combo, event, virtual_keyboard_active)) {
70
82
  // Find the first enabled command and execute it
71
83
  const enabled_command = commands.find((cmd) => cmd.is_enabled());
72
84
  if (enabled_command) {
package/src/lib/utils.js CHANGED
@@ -5,20 +5,41 @@
5
5
  const SEGMENTER = new Intl.Segmenter('en', { granularity: 'grapheme' });
6
6
 
7
7
  /**
8
- * Detect if the current browser is on a mobile device
8
+ * Detect if the virtual keyboard is likely visible.
9
+ *
10
+ * This is a heuristic based on the visual viewport becoming smaller than the
11
+ * layout viewport.
12
+ *
13
+ * @returns {boolean} true if the virtual keyboard is likely active, false otherwise
14
+ */
15
+ export function is_virtual_keyboard_active() {
16
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
17
+ return false;
18
+ }
19
+
20
+ const visual_viewport = window.visualViewport;
21
+ if (!visual_viewport) {
22
+ return false;
23
+ }
24
+
25
+ return visual_viewport.height < document.documentElement.clientHeight;
26
+ }
27
+
28
+ /**
29
+ * Detect if the current browser is likely on a mobile device.
30
+ *
31
+ * This uses the user agent only so touch-capable laptops are not treated as
32
+ * mobile browsers.
33
+ *
9
34
  * @returns {boolean} true if mobile browser, false otherwise
10
35
  */
11
36
  export function is_mobile_browser() {
12
- if (typeof window === 'undefined' || typeof navigator === 'undefined') {
37
+ if (typeof navigator === 'undefined') {
13
38
  return false;
14
39
  }
15
40
 
16
41
  const user_agent = navigator.userAgent;
17
- return (
18
- /iPhone|iPad|iPod|Android|Mobile/i.test(user_agent) ||
19
- 'ontouchstart' in window ||
20
- navigator.maxTouchPoints > 0
21
- );
42
+ return /iPhone|iPad|iPod|Android|Mobile/i.test(user_agent);
22
43
  }
23
44
 
24
45
  // ‼️‼️‼️‼️‼️‼️ UNUSED UTILITY BELOW ‼️‼️‼️‼️‼️‼️