1
1
import { Maoka , maoka } from "@ordo-pink/maoka"
2
+ import { create_hotkey_from_event } from "@ordo-pink/hotkey-from-event"
2
3
import { maoka_jabs } from "@ordo-pink/maoka-jabs"
4
+ import { sweech } from "@ordo-pink/sweech"
5
+ import { title_case } from "@ordo-pink/tau"
3
6
4
- import { app_context } from "@ordo-pink/frontend-app /app-context"
7
+ import { app_context } from "../.. /app-context"
5
8
import { command_palette$ } from "./command-palette.state"
6
9
7
- export const command_palette = ( ) =>
8
- internal . command_palette_wrapper ( ( ) =>
9
- internal . command_palette_window ( ( ) => [
10
- internal . command_palette_search ( ) ,
11
- internal . command_palette_items_div ( ( ) => ( ) => "Items" ) ,
12
- internal . command_palette_footer_div ( ( ) => ( ) => "Footer" ) ,
13
- ] ) ,
14
- )
10
+ export const command_palette = ( ) => internal . overlay ( internal . modal )
15
11
16
12
namespace internal {
17
- export const command_palette_wrapper : Maoka . Teacher = kindergarten =>
18
- styled . command_palette_wrapper ( use => {
13
+ export const overlay : Maoka . Teacher = kindergarten =>
14
+ styled . overlay ( use => {
19
15
const { hunter } = use ( app_context . consume )
20
16
21
17
const handle_show = ( ) => use ( maoka_jabs . add_class ( "active" ) )
@@ -29,21 +25,59 @@ namespace internal {
29
25
return kindergarten
30
26
} )
31
27
32
- export const command_palette_window : Maoka . Teacher = kindergarten =>
33
- styled . command_palette_window ( use => {
34
- const handle_click = ( event : MouseEvent ) => event . stopPropagation ( )
35
-
28
+ export const modal = ( ) =>
29
+ styled . modal ( use => {
36
30
const get_current = use ( maoka_jabs . cheat$ ( command_palette$ , "current" as const ) )
37
31
32
+ const handle_click = ( event : MouseEvent ) => event . stopPropagation ( )
33
+
38
34
use ( maoka_jabs . set_id ( "cp" ) )
39
35
use ( maoka_jabs . listen ( "onclick" , handle_click ) )
40
36
41
- return ( ) => get_current ( ) && kindergarten ( )
37
+ return ( ) => {
38
+ const current = get_current ( )
39
+
40
+ if ( ! current ) return null
41
+
42
+ return [
43
+ search ( ) ,
44
+ // TODO open via route fragment and query
45
+ // TODO create subitems if item is found with fuzzy search but the match is not exact
46
+ current . is_multiple
47
+ ? items_wrapper ( ( ) => [ items ( ( ) => current . items . map ( item ) ) , items ( ( ) => current . pinned_items ?. map ( item ) ) ] )
48
+ : items ( ( ) => current . items . map ( item ) ) ,
49
+ styled . footer ( ( ) => ( ) => "Footer" ) ,
50
+ ]
51
+ }
52
+ } )
53
+
54
+ const item = ( item : Ordo . CommandPalette . Item ) =>
55
+ styled . item ( use => {
56
+ const handle_click = ( ) => item . value ( )
57
+
58
+ use ( maoka_jabs . set_id ( String ( item . id ) ) )
59
+ use ( maoka_jabs . set_attribute ( "title" , item . description ) )
60
+ use ( maoka_jabs . listen ( "onclick" , handle_click ) )
61
+
62
+ return ( ) => [
63
+ item_main ( ( ) => [
64
+ styled . item_title_wrapper ( ( ) => ( ) => [ item . render_icon && item_icon_span ( item . render_icon ) , item . readable_name ] ) ,
65
+ item . hotkey && styled . item_info ( ( ) => ( ) => hotkey ( item . hotkey ! , { decoration_only : true } ) ) ,
66
+ ] ) ,
67
+ item_footer ( ( ) => item . description ) ,
68
+ ]
42
69
} )
43
70
44
- export const command_palette_search = ( ) =>
45
- command_palette_form ( ( ) =>
46
- styled . command_palette_input ( use => {
71
+ const item_main : Maoka . Teacher = kindergarten => styled . item_main ( ( ) => kindergarten )
72
+
73
+ const item_footer : Maoka . Teacher = kindergarten => styled . item_footer ( ( ) => kindergarten )
74
+
75
+ const item_icon_span = ( render_icon : Ordo . CommandPalette . RenderIcon ) =>
76
+ maoka . create ( "span" , use => use ( maoka . jabs . if_dom ( n => void render_icon ( n . value ) ) ) )
77
+
78
+ const search = ( ) =>
79
+ form ( ( ) =>
80
+ styled . item_input ( use => {
47
81
const t_search = "Search..." // TODO i18n
48
82
49
83
const handle_mount = ( ) => use ( maoka . jabs . if_dom ( n => n . value . focus ( ) ) )
@@ -55,8 +89,8 @@ namespace internal {
55
89
} ) ,
56
90
)
57
91
58
- const command_palette_form : Maoka . Teacher = kindergarten =>
59
- styled . command_palette_form ( use => {
92
+ const form : Maoka . Teacher = kindergarten =>
93
+ styled . item_form ( use => {
60
94
const handle_submit = ( event : Event ) => event . preventDefault ( )
61
95
62
96
use ( maoka_jabs . set_id ( "cp-form" ) )
@@ -65,13 +99,108 @@ namespace internal {
65
99
return kindergarten
66
100
} )
67
101
68
- export const command_palette_items_div = maoka . styled . div ( "command-palette_items" )
69
- export const command_palette_footer_div = maoka . styled . div ( "command-palette_footer" )
102
+ const items_wrapper : Maoka . Teacher = kindergarten => styled . items_wrapper ( ( ) => kindergarten )
103
+
104
+ const items : Maoka . Teacher = kindergarten => styled . items ( ( ) => kindergarten )
70
105
71
106
namespace styled {
72
- export const command_palette_form = maoka . styled . form ( "command-palette_form" )
73
- export const command_palette_input = maoka . styled . input ( "command-palette_search" )
74
- export const command_palette_window = maoka . styled . div ( "command-palette" )
75
- export const command_palette_wrapper = maoka . styled . div ( "command-palette_wrapper" )
107
+ export const items_wrapper = maoka . styled . div ( "command-palette_items_multiple-wrapper" )
108
+ export const items = maoka . styled . div ( "command-palette_items" )
109
+ export const item = maoka . styled . div ( "command-palette_item" )
110
+ export const item_info = maoka . styled . div ( "command-palette_item_info" )
111
+ export const item_title_wrapper = maoka . styled . div ( "command-palette_item_title-wrapper" )
112
+ export const item_main = maoka . styled . div ( "command-palette_item_main" )
113
+ export const item_footer = maoka . styled . div ( "command-palette_item_footer" )
114
+ export const item_form = maoka . styled . form ( "command-palette_form" )
115
+ export const item_input = maoka . styled . input ( "command-palette_search" )
116
+ export const modal = maoka . styled . div ( "command-palette" )
117
+ export const overlay = maoka . styled . div ( "command-palette_wrapper" )
118
+ export const footer = maoka . styled . div ( "command-palette_footer" )
76
119
}
77
120
}
121
+
122
+ // TODO Move to core
123
+
124
+ export type HotkeyOptions = {
125
+ prevent_in_inputs ?: boolean
126
+ prevent_in_contenteditable ?: boolean
127
+ decoration_only ?: boolean
128
+ show_in_mobile ?: boolean
129
+ }
130
+
131
+ const hotkey = ( hotkey : string , options ?: HotkeyOptions ) =>
132
+ hotkey_div ( ( use , node ) => {
133
+ const split = hotkey . split ( "+" )
134
+
135
+ const is_darwin = use ( maoka_jabs . is_darwin )
136
+
137
+ const meta = is_darwin ? Key ( "⌥" ) : Key ( "Alt" )
138
+ const mod = is_darwin ? Key ( "⌘" ) : Key ( "Ctrl" )
139
+ const ctrl = Key ( "Ctrl" )
140
+ const option = Key ( "⌥" )
141
+ const shift = Key ( "⇧" )
142
+
143
+ const symbol = split [ split . length - 1 ] . toLowerCase ( )
144
+
145
+ const handle_mount = ( ) => {
146
+ const handle_keydown = ( event : KeyboardEvent ) => {
147
+ if ( IGNORED_KEYS . includes ( event . key ) || options ?. decoration_only ) return
148
+
149
+ if ( options ?. prevent_in_inputs ) {
150
+ const target = event . target as HTMLElement
151
+
152
+ // TODO Add textarea and div contenteditable
153
+ if ( target . tagName === "INPUT" ) return
154
+ }
155
+
156
+ const parsed_hotkey = create_hotkey_from_event ( event , is_darwin )
157
+
158
+ if ( parsed_hotkey === hotkey ) {
159
+ event . preventDefault ( )
160
+
161
+ if ( node . value instanceof globalThis . HTMLElement ) node . value . click ( )
162
+ }
163
+ }
164
+
165
+ document . addEventListener ( "keydown" , handle_keydown )
166
+
167
+ return ( ) => {
168
+ document . removeEventListener ( "keydown" , handle_keydown )
169
+ }
170
+ }
171
+
172
+ if ( options ?. show_in_mobile ) use ( maoka_jabs . add_class ( "mobile" ) )
173
+ if ( ! options ?. decoration_only ) use ( maoka . jabs . onmount ( handle_mount ) )
174
+
175
+ return ( ) => [
176
+ split . includes ( "ctrl" ) ? ctrl : void 0 ,
177
+ split . includes ( "meta" ) ? meta : void 0 ,
178
+ split . includes ( "option" ) ? option : void 0 ,
179
+ split . includes ( "mod" ) ? mod : void 0 ,
180
+ split . includes ( "shift" ) ? shift : void 0 ,
181
+
182
+ Key ( symbol ) ,
183
+ ]
184
+ } )
185
+
186
+ const hotkey_div = maoka . styled . div ( "hotkey" )
187
+
188
+ const IGNORED_KEYS = [ "Control" , "Shift" , "Alt" , "Meta" ]
189
+
190
+ const KeyContainer = maoka . styled . span ( "key-container" )
191
+
192
+ const Key = ( key : string ) =>
193
+ KeyContainer (
194
+ ( ) => ( ) =>
195
+ sweech
196
+ . match ( key )
197
+ . case ( "backspace" , ( ) => "⌫" )
198
+ . case ( "enter" , ( ) => "⏎" )
199
+ . case ( "escape" , ( ) => "Esc" )
200
+ . case ( "tab" , ( ) => "⇥" )
201
+ . case ( "arrowleft" , ( ) => "←" )
202
+ . case ( "arrowright" , ( ) => "→" )
203
+ . case ( "arrowup" , ( ) => "↑" )
204
+ . case ( "arrowdown" , ( ) => "↓" )
205
+ . default ( ( ) => title_case ( key ) ) ,
206
+ )
0 commit comments