@globalbrain/sefirot 4.59.1 → 4.61.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.
@@ -17,6 +17,75 @@ export function isTextLikeInput(target: EventTarget | null): boolean {
17
17
  return ['text', 'search', 'url', 'email', 'tel', 'password', 'number'].includes(type)
18
18
  }
19
19
 
20
+ export type EditorSubmitShortcut = 'enter' | 'command-enter' | 'control-enter'
21
+
22
+ /**
23
+ * The user-facing shortcut to show for the currently focused editor control.
24
+ * Plain Enter is truthful only for simple text-like inputs; other controls need
25
+ * the platform's primary modifier to avoid clashing with their own Enter key
26
+ * behavior. A text input nested inside a dropdown (its search filter, see
27
+ * `SDropdownSectionFilter` / `SInputAsyncDropdown`) keeps Enter to itself
28
+ * too, so only the modifier gesture reaches the editor from there.
29
+ */
30
+ export function editorSubmitShortcutForTarget(
31
+ target: EventTarget | null,
32
+ platform = currentPlatform()
33
+ ): EditorSubmitShortcut {
34
+ if (
35
+ isTextLikeInput(target)
36
+ && !(target as HTMLElement).closest('.SDropdown, .SInputAsyncDropdown')
37
+ ) {
38
+ return 'enter'
39
+ }
40
+
41
+ return hasCommandShortcutModifier(platform) ? 'command-enter' : 'control-enter'
42
+ }
43
+
44
+ const commandShortcutPlatforms = new Set([
45
+ 'ios',
46
+ 'ipados',
47
+ 'macos',
48
+
49
+ // `navigator.platform` fallback values.
50
+ 'ipad',
51
+ 'iphone',
52
+ 'macintel'
53
+ ])
54
+
55
+ function hasCommandShortcutModifier(platform: string): boolean {
56
+ return commandShortcutPlatforms.has(platform.toLowerCase())
57
+ }
58
+
59
+ function currentPlatform(): string {
60
+ if (typeof navigator === 'undefined') {
61
+ return ''
62
+ }
63
+
64
+ const nav = navigator as Navigator & { userAgentData?: { platform?: string } }
65
+
66
+ return nav.userAgentData?.platform ?? nav.platform ?? ''
67
+ }
68
+
69
+ /**
70
+ * Keydown handler for a text input nested inside a dropdown-like control (a
71
+ * search filter): Enter operates the control, not an enclosing inline editor,
72
+ * so it is stopped — with its default prevented — unless it carries the
73
+ * universal submit modifier (Cmd/Ctrl, see {@link isEditorSubmitKeydown}).
74
+ * Shift/Alt+Enter are contained too: the editor's submit predicate would read
75
+ * them off a text-like input as a plain submitting Enter. The Enter that
76
+ * commits an IME composition belongs to the IME — swallowing its default would
77
+ * drop the composed text — so it passes untouched (the editor ignores it).
78
+ */
79
+ export function stopNonSubmitEnterKeydown(event: KeyboardEvent): void {
80
+ if (event.isComposing) {
81
+ return
82
+ }
83
+ if (event.key === 'Enter' && !event.metaKey && !event.ctrlKey) {
84
+ event.stopPropagation()
85
+ event.preventDefault()
86
+ }
87
+ }
88
+
20
89
  /**
21
90
  * Whether an Enter keydown should submit an inline editor. Ctrl/Cmd+Enter
22
91
  * submits from any control — it's the near-universal "submit" gesture (GitHub,
@@ -65,8 +134,9 @@ export function isEditorCancelKeydown(event: KeyboardEvent): boolean {
65
134
  * throw on `formInputComponent()` (not yet editable). When making one editable,
66
135
  * if its input is a composite control with its own nested text input (e.g. a
67
136
  * dropdown's search filter), that nested input must keep Enter/Escape from
68
- * bubbling to this handler — see `SDropdownSectionFilter` otherwise typing a
69
- * value and pressing Enter would submit/cancel the whole editor.
137
+ * bubbling to this handler — {@link stopNonSubmitEnterKeydown} is the Enter
138
+ * half; see `SDropdownSectionFilter` otherwise typing a value and pressing
139
+ * Enter would submit/cancel the whole editor.
70
140
  */
71
141
  export function dispatchEditorKeydown(
72
142
  event: KeyboardEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "4.59.1",
3
+ "version": "4.61.0",
4
4
  "description": "Vue Components for Global Brain Design System.",
5
5
  "keywords": [
6
6
  "components",
@@ -56,11 +56,12 @@
56
56
  "release": "npm whoami >/dev/null 2>&1 || npm login && release-it"
57
57
  },
58
58
  "dependencies": {
59
+ "@iconify-json/lucide": "^1.2.116",
59
60
  "@iconify-json/ph": "^1.2.2",
60
61
  "@iconify-json/ri": "^1.2.10",
61
62
  "@popperjs/core": "^2.11.8",
62
- "@sentry/browser": "^10.62.0",
63
- "@sentry/vue": "^10.62.0",
63
+ "@sentry/browser": "^10.63.0",
64
+ "@sentry/vue": "^10.63.0",
64
65
  "@tanstack/vue-virtual": "3.0.0-beta.62",
65
66
  "@tinyhttp/content-disposition": "^2.2.4",
66
67
  "@tinyhttp/cookie": "^2.1.1",
@@ -84,11 +85,11 @@
84
85
  "jsdom": "^29.1.1",
85
86
  "lodash-es": "^4.18.1",
86
87
  "magic-string": "^0.30.21",
87
- "markdown-it": "^14.2.0",
88
+ "markdown-it": "^14.3.0",
88
89
  "normalize.css": "^8.0.1",
89
90
  "ofetch": "^1.5.1",
90
91
  "pinia": "^3.0.4",
91
- "postcss": "^8.5.15",
92
+ "postcss": "^8.5.16",
92
93
  "postcss-nested": "^7.0.2",
93
94
  "punycode": "^2.3.1",
94
95
  "qs": "^6.15.3",
@@ -104,8 +105,8 @@
104
105
  "@histoire/plugin-vue": "1.0.0-beta.1",
105
106
  "@release-it/conventional-changelog": "^11.0.1",
106
107
  "@types/jsdom": "^28.0.3",
107
- "@types/node": "^26.0.1",
108
- "@typescript-eslint/rule-tester": "^8.62.0",
108
+ "@types/node": "^26.1.0",
109
+ "@typescript-eslint/rule-tester": "^8.62.1",
109
110
  "@vitest/coverage-v8": "^4.1.9",
110
111
  "@vue/test-utils": "^2.4.11",
111
112
  "eslint": "^9.39.4",
@@ -115,7 +116,7 @@
115
116
  "typescript": "~6.0.3",
116
117
  "vitepress": "^2.0.0-alpha.17",
117
118
  "vitest": "^4.1.9",
118
- "vue-tsc": "^3.3.5"
119
+ "vue-tsc": "^3.3.6"
119
120
  },
120
121
  "packageManager": "pnpm@11.9.0"
121
122
  }