@rokkit/actions 1.0.0-next.107 → 1.0.0-next.109
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/package.json +1 -1
- package/src/navigator.svelte.js +84 -237
- package/src/types.js +13 -0
- package/src/utils.js +136 -9
package/package.json
CHANGED
package/src/navigator.svelte.js
CHANGED
|
@@ -1,259 +1,106 @@
|
|
|
1
1
|
import { on } from 'svelte/events'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
vertical: {
|
|
19
|
-
prev: ['ArrowUp'],
|
|
20
|
-
next: ['ArrowDown'],
|
|
21
|
-
collapse: ['ArrowLeft'],
|
|
22
|
-
expand: ['ArrowRight'],
|
|
23
|
-
select: ['Enter'],
|
|
24
|
-
toggle: ['Space'],
|
|
25
|
-
delete: ['Delete', 'Backspace']
|
|
26
|
-
}
|
|
2
|
+
import { omit } from 'ramda'
|
|
3
|
+
import { getClickAction, getKeyboardAction, getPathFromEvent } from './utils'
|
|
4
|
+
|
|
5
|
+
const defaultOptions = { horizontal: false, nested: false, enabled: true }
|
|
6
|
+
|
|
7
|
+
const EVENT_MAP = {
|
|
8
|
+
first: ['move'],
|
|
9
|
+
last: ['move'],
|
|
10
|
+
previous: ['move'],
|
|
11
|
+
next: ['move'],
|
|
12
|
+
select: ['move', 'select'],
|
|
13
|
+
extend: ['move', 'select'],
|
|
14
|
+
collapse: ['toggle'],
|
|
15
|
+
expand: ['toggle'],
|
|
16
|
+
toggle: ['toggle']
|
|
27
17
|
}
|
|
28
18
|
|
|
29
19
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
20
|
+
* The last only indicates that if there is an array only the last event is fired.
|
|
21
|
+
* This is crucial because a click event needs to fire both move and select,
|
|
22
|
+
* however the keyboard should only fire the select event because we are already
|
|
23
|
+
* on the current item
|
|
24
|
+
*
|
|
25
|
+
* @param {HTMLElement} root
|
|
26
|
+
* @param {*} controller
|
|
27
|
+
* @param {*} name
|
|
32
28
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* @param {HTMLElement} root - The root element
|
|
36
|
-
* @param {Object} wrapper - The data wrapper object
|
|
37
|
-
* @param {Object} options - Configuration options
|
|
38
|
-
*/
|
|
39
|
-
constructor(root, wrapper, options = {}) {
|
|
40
|
-
this.root = root
|
|
41
|
-
this.wrapper = wrapper
|
|
42
|
-
this.options = options
|
|
43
|
-
this.keyMappings = KEY_MAPPINGS[options?.direction || 'vertical']
|
|
44
|
-
|
|
45
|
-
this.handleKeyUp = this.handleKeyUp.bind(this)
|
|
46
|
-
this.handleClick = this.handleClick.bind(this)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Initialize event listeners
|
|
51
|
-
*/
|
|
52
|
-
init() {
|
|
53
|
-
return [on(this.root, 'keyup', this.handleKeyUp), on(this.root, 'click', this.handleClick)]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Check if modifier keys are pressed
|
|
58
|
-
* @param {Event} event - The event object
|
|
59
|
-
* @returns {boolean} - Whether modifier keys are pressed
|
|
60
|
-
*/
|
|
61
|
-
hasModifierKey(event) {
|
|
62
|
-
return event.ctrlKey || event.metaKey || event.shiftKey
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Handle keyboard events
|
|
67
|
-
* @param {KeyboardEvent} event - The keyboard event
|
|
68
|
-
*/
|
|
69
|
-
handleKeyUp(event) {
|
|
70
|
-
const { key } = event
|
|
71
|
-
|
|
72
|
-
const eventName = getEventForKey(this.keyMappings, key)
|
|
73
|
-
|
|
74
|
-
if (!eventName) return
|
|
75
|
-
|
|
76
|
-
const handled = this.processKeyAction(eventName, event)
|
|
77
|
-
|
|
78
|
-
if (handled) {
|
|
79
|
-
event.stopPropagation()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Process a key action based on its type
|
|
85
|
-
* @param {string} eventName - The mapped event name
|
|
86
|
-
* @param {KeyboardEvent} event - The original keyboard event
|
|
87
|
-
* @returns {boolean} - Whether the event was handled
|
|
88
|
-
*/
|
|
89
|
-
processKeyAction(eventName, event) {
|
|
90
|
-
const actionHandlers = {
|
|
91
|
-
prev: () => this.handleNavigationKey('prev', this.hasModifierKey(event)),
|
|
92
|
-
next: () => this.handleNavigationKey('next', this.hasModifierKey(event)),
|
|
93
|
-
expand: () => this.executeAction('expand'),
|
|
94
|
-
collapse: () => this.executeAction('collapse'),
|
|
95
|
-
select: () => this.executeAction('select'),
|
|
96
|
-
toggle: () => this.executeAction('extend'),
|
|
97
|
-
delete: () => this.executeAction('delete')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const handler = actionHandlers[eventName]
|
|
101
|
-
if (handler) {
|
|
102
|
-
return handler() !== false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return false
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Handle navigation key presses (arrows)
|
|
110
|
-
* @param {string} direction - The direction to move ('prev' or 'next')
|
|
111
|
-
* @param {boolean} hasModifier - Whether modifier keys are pressed
|
|
112
|
-
* @returns {boolean} - Whether the action was handled
|
|
113
|
-
*/
|
|
114
|
-
handleNavigationKey(direction, hasModifier) {
|
|
115
|
-
// First move in the specified direction
|
|
116
|
-
const moved = this.executeAction(direction)
|
|
117
|
-
|
|
118
|
-
if (!moved) return false
|
|
119
|
-
|
|
120
|
-
// If modifier key is pressed and multiSelect is enabled, extend selection
|
|
121
|
-
if (hasModifier && this.wrapper.multiSelect) {
|
|
122
|
-
this.executeAction('extend')
|
|
123
|
-
} else {
|
|
124
|
-
// Otherwise just select the current item
|
|
125
|
-
this.executeAction('select')
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Always emit a move event for navigation
|
|
129
|
-
this.emitActionEvent('move')
|
|
130
|
-
return true
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Handle click events
|
|
135
|
-
* @param {MouseEvent} event - The click event
|
|
136
|
-
*/
|
|
137
|
-
handleClick(event) {
|
|
138
|
-
const node = getClosestAncestorWithAttribute(event.target, 'data-path')
|
|
139
|
-
|
|
140
|
-
if (!node) return
|
|
141
|
-
|
|
142
|
-
const path = getPathFromKey(node.getAttribute('data-path'))
|
|
143
|
-
// Check if click was on a toggle icon
|
|
144
|
-
const toggleIconClicked = this.handleToggleIconClick(path, event.target)
|
|
145
|
-
|
|
146
|
-
if (toggleIconClicked) return
|
|
147
|
-
|
|
148
|
-
// Move to the clicked item
|
|
149
|
-
this.wrapper.moveTo(path)
|
|
29
|
+
export function emitAction(root, controller, name, lastOnly = false) {
|
|
30
|
+
const events = lastOnly ? EVENT_MAP[name].slice(-1) : EVENT_MAP[name]
|
|
150
31
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.executeAction('extend', path)
|
|
154
|
-
} else {
|
|
155
|
-
this.executeAction('select', path)
|
|
156
|
-
}
|
|
157
|
-
this.executeAction('toggle', path)
|
|
158
|
-
event.stopPropagation()
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Handle clicks on toggle icons
|
|
163
|
-
* @param {number[]} path - The path of the item to perform the action on
|
|
164
|
-
* @param {HTMLElement} target - The clicked element
|
|
165
|
-
* @returns {boolean} - Whether a toggle icon was clicked and handled
|
|
166
|
-
*/
|
|
167
|
-
handleToggleIconClick(path, target) {
|
|
168
|
-
const isIcon = target.tagName.toLowerCase() === 'rk-icon'
|
|
169
|
-
const state = target.getAttribute('data-state')
|
|
170
|
-
if (!isIcon || !['closed', 'opened'].includes(state)) return false
|
|
171
|
-
|
|
172
|
-
return this.executeAction('toggle', path)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Execute an action if available
|
|
177
|
-
* @param {string} actionName - The name of the action to execute
|
|
178
|
-
* @param {number[]} path - The path of the item to perform the action on
|
|
179
|
-
* @returns {boolean} - Whether the action was executed
|
|
180
|
-
*/
|
|
181
|
-
executeAction(actionName, path) {
|
|
182
|
-
// Get the action function based on action name
|
|
183
|
-
const action = this.getActionFunction(actionName)
|
|
184
|
-
|
|
185
|
-
if (action) {
|
|
186
|
-
const executed = path ? action(path) : action()
|
|
187
|
-
if (executed) this.emitActionEvent(actionName)
|
|
188
|
-
|
|
189
|
-
return executed
|
|
190
|
-
}
|
|
191
|
-
return false
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Get the appropriate action function based on action name and conditions
|
|
196
|
-
* @param {string} actionName - The name of the action
|
|
197
|
-
* @returns {Function|null} - The action function or null if not available
|
|
198
|
-
*/
|
|
199
|
-
getActionFunction(actionName) {
|
|
200
|
-
// Basic navigation and selection actions always available
|
|
201
|
-
const actions = {
|
|
202
|
-
prev: () => this.wrapper.movePrev(),
|
|
203
|
-
next: () => this.wrapper.moveNext(),
|
|
204
|
-
select: (path) => this.wrapper.select(path),
|
|
205
|
-
collapse: (path) => this.wrapper.collapse(path),
|
|
206
|
-
expand: (path) => this.wrapper.expand(path),
|
|
207
|
-
toggle: (path) => this.wrapper.toggleExpansion(path),
|
|
208
|
-
extend: (path) => this.wrapper.extendSelection(path),
|
|
209
|
-
delete: () => this.wrapper.delete()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return actions[actionName] || null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Emit an action event
|
|
217
|
-
* @param {string} eventName - The name of the event to emit
|
|
218
|
-
*/
|
|
219
|
-
emitActionEvent(eventName) {
|
|
220
|
-
const data = {
|
|
221
|
-
path: this.wrapper.currentNode?.path,
|
|
222
|
-
value: this.wrapper.currentNode?.value
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// For select events, include selected nodes
|
|
226
|
-
if (['select', 'extend'].includes(eventName)) {
|
|
227
|
-
data.selected = Array.from(this.wrapper.selectedNodes.values()).map((node) => node.value)
|
|
228
|
-
// Normalize selection events to 'select'
|
|
229
|
-
eventName = 'select'
|
|
230
|
-
} else if (['prev', 'next'].includes(eventName)) {
|
|
231
|
-
eventName = 'move'
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
this.root.dispatchEvent(
|
|
32
|
+
events.forEach((event) => {
|
|
33
|
+
root.dispatchEvent(
|
|
235
34
|
new CustomEvent('action', {
|
|
236
35
|
detail: {
|
|
237
|
-
|
|
238
|
-
data
|
|
36
|
+
name: event,
|
|
37
|
+
data: { value: controller.focused, selected: controller.selected }
|
|
239
38
|
}
|
|
240
39
|
})
|
|
241
40
|
)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Generic action handler for keyboard events.
|
|
46
|
+
*
|
|
47
|
+
* @param {Record<string, () => void>} actions
|
|
48
|
+
* @param {KeyboardEvent} event
|
|
49
|
+
*/
|
|
50
|
+
export function handleAction(event, handler, path) {
|
|
51
|
+
if (handler) {
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
event.stopPropagation()
|
|
54
|
+
|
|
55
|
+
return handler(path)
|
|
242
56
|
}
|
|
57
|
+
return false
|
|
243
58
|
}
|
|
244
59
|
|
|
60
|
+
function getHandlers(wrapper) {
|
|
61
|
+
return {
|
|
62
|
+
first: () => wrapper.moveFirst(),
|
|
63
|
+
last: () => wrapper.moveLast(),
|
|
64
|
+
previous: () => wrapper.movePrev(),
|
|
65
|
+
next: () => wrapper.moveNext(),
|
|
66
|
+
collapse: () => wrapper.collapse(),
|
|
67
|
+
expand: () => wrapper.expand(),
|
|
68
|
+
select: (path) => wrapper.select(path),
|
|
69
|
+
extend: (path) => wrapper.extendSelection(path),
|
|
70
|
+
toggle: (path) => wrapper.toggleExpansion(path)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
245
73
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* @param {
|
|
74
|
+
* A svelte action function that captures keyboard evvents and emits event for corresponding movements.
|
|
75
|
+
*
|
|
76
|
+
* @param {HTMLElement} node
|
|
77
|
+
* @param {import('./types').NavigableOptions} options
|
|
78
|
+
* @returns {import('./types').SvelteActionReturn}
|
|
249
79
|
*/
|
|
250
|
-
export function navigator(
|
|
251
|
-
const
|
|
80
|
+
export function navigator(node, options) {
|
|
81
|
+
const { wrapper } = options
|
|
82
|
+
const config = { ...defaultOptions, ...omit(['wrapper'], options) }
|
|
83
|
+
const handlers = getHandlers(wrapper)
|
|
84
|
+
|
|
85
|
+
const handleKeydown = (event) => {
|
|
86
|
+
const action = getKeyboardAction(event, config)
|
|
87
|
+
const handled = handleAction(event, handlers[action])
|
|
88
|
+
if (handled) emitAction(node, options.wrapper, action, true)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const handleClick = (event) => {
|
|
92
|
+
const action = getClickAction(event)
|
|
93
|
+
const path = getPathFromEvent(event)
|
|
94
|
+
const handled = handleAction(event, handlers[action], path)
|
|
95
|
+
|
|
96
|
+
if (handled) emitAction(node, options.wrapper, action)
|
|
97
|
+
}
|
|
252
98
|
|
|
253
99
|
$effect(() => {
|
|
254
|
-
const
|
|
100
|
+
const cleanup = [on(node, 'keyup', handleKeydown), on(node, 'click', handleClick)]
|
|
101
|
+
|
|
255
102
|
return () => {
|
|
256
|
-
|
|
103
|
+
cleanup.forEach((fn) => fn())
|
|
257
104
|
}
|
|
258
105
|
})
|
|
259
106
|
}
|
package/src/types.js
CHANGED
|
@@ -48,3 +48,16 @@
|
|
|
48
48
|
* @property {Navigator} wrapper - Whether the navigator is enabled
|
|
49
49
|
* @property {NavigatorOptions} options - Whether the navigator is vertical or horizontal
|
|
50
50
|
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {Object} Controller
|
|
54
|
+
* @property {Function} moveNext
|
|
55
|
+
* @property {Function} movePrev
|
|
56
|
+
* @property {Function} moveFirst
|
|
57
|
+
* @property {Function} moveLast
|
|
58
|
+
* @property {Function} [expand]
|
|
59
|
+
* @property {Function} [collapse]
|
|
60
|
+
* @property {Function} select
|
|
61
|
+
* @property {Function} extendSelection
|
|
62
|
+
* @property {Function} [toggleExpansion]
|
|
63
|
+
*/
|
package/src/utils.js
CHANGED
|
@@ -49,20 +49,147 @@ export function handleAction(actions, event) {
|
|
|
49
49
|
* @param {import('./types').NavigableHandlers} handlers
|
|
50
50
|
*/
|
|
51
51
|
export function getKeyboardActions(options, handlers) {
|
|
52
|
+
const { horizontal, nested } = options
|
|
52
53
|
if (!options.enabled) return {}
|
|
53
54
|
|
|
54
|
-
const
|
|
55
|
+
const common = {
|
|
56
|
+
Enter: handlers.select,
|
|
57
|
+
' ': handlers.select
|
|
58
|
+
}
|
|
59
|
+
const movement = horizontal
|
|
55
60
|
? { ArrowLeft: handlers.previous, ArrowRight: handlers.next }
|
|
56
61
|
: { ArrowUp: handlers.previous, ArrowDown: handlers.next }
|
|
57
|
-
const change =
|
|
58
|
-
?
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
const change = horizontal
|
|
63
|
+
? { ArrowUp: handlers.collapse, ArrowDown: handlers.expand }
|
|
64
|
+
: { ArrowLeft: handlers.collapse, ArrowRight: handlers.expand }
|
|
65
|
+
|
|
66
|
+
if (nested) return { ...common, ...movement, ...change }
|
|
67
|
+
return { ...common, ...movement }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Finds and returns an index path based on data-path attribute
|
|
72
|
+
*
|
|
73
|
+
* @param {MouseEvent} event
|
|
74
|
+
* @returns {number[]|null} null or index path array
|
|
75
|
+
*/
|
|
76
|
+
export function getPathFromEvent(event) {
|
|
77
|
+
const node = getClosestAncestorWithAttribute(event.target, 'data-path')
|
|
78
|
+
return node?.getAttribute('data-path')
|
|
79
|
+
// return node ? getPathFromKey(node.getAttribute('data-path')) : null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a keyboard action mapping based on navigation options
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} options - Navigation options
|
|
86
|
+
* @param {boolean} options.horizontal - Whether navigation is horizontal
|
|
87
|
+
* @param {boolean} options.nested - Whether navigation is nested
|
|
88
|
+
* @returns {Object} Mapping of keys to actions
|
|
89
|
+
*/
|
|
90
|
+
function createKeyboardActionMap(options) {
|
|
91
|
+
const { horizontal, nested } = options
|
|
92
|
+
|
|
93
|
+
// Define movement actions based on horizontal option
|
|
94
|
+
const movementActions = horizontal
|
|
95
|
+
? { ArrowLeft: 'previous', ArrowRight: 'next' }
|
|
96
|
+
: { ArrowUp: 'previous', ArrowDown: 'next' }
|
|
97
|
+
|
|
98
|
+
// Define expand/collapse actions for nested option
|
|
99
|
+
const nestedActions = nested
|
|
100
|
+
? horizontal
|
|
101
|
+
? { ArrowUp: 'collapse', ArrowDown: 'expand' }
|
|
102
|
+
: { ArrowLeft: 'collapse', ArrowRight: 'expand' }
|
|
61
103
|
: {}
|
|
104
|
+
|
|
105
|
+
// Common actions regardless of options
|
|
106
|
+
const commonActions = {
|
|
107
|
+
Enter: 'select',
|
|
108
|
+
' ': 'select',
|
|
109
|
+
Home: 'first',
|
|
110
|
+
End: 'last'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Combine all possible actions
|
|
62
114
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...
|
|
66
|
-
...change
|
|
115
|
+
...commonActions,
|
|
116
|
+
...movementActions,
|
|
117
|
+
...nestedActions
|
|
67
118
|
}
|
|
68
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Creates a keyboard action mapping based on navigation options
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} options - Navigation options
|
|
125
|
+
* @param {boolean} options.horizontal - Whether navigation is horizontal
|
|
126
|
+
* @param {boolean} options.nested - Whether navigation is nested
|
|
127
|
+
* @returns {Object} Mapping of keys to actions
|
|
128
|
+
*/
|
|
129
|
+
function createModifierKeyboardActionMap(options) {
|
|
130
|
+
const { horizontal } = options
|
|
131
|
+
const common = { ' ': 'extend', Home: 'first', End: 'last' }
|
|
132
|
+
const directional = horizontal
|
|
133
|
+
? { ArrowLeft: 'first', ArrowRight: 'last' }
|
|
134
|
+
: { ArrowUp: 'first', ArrowDown: 'last' }
|
|
135
|
+
return { ...common, ...directional }
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Identifies if an element is a collapsible icon
|
|
139
|
+
* @param {HTMLElement} target
|
|
140
|
+
* @returns
|
|
141
|
+
*/
|
|
142
|
+
function isNodeToggle(target) {
|
|
143
|
+
return (
|
|
144
|
+
target &&
|
|
145
|
+
target.getAttribute('data-tag') === 'icon' &&
|
|
146
|
+
['closed', 'opened'].includes(target.getAttribute('data-state'))
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Determines an action based on a keyboard event and navigation options
|
|
151
|
+
*
|
|
152
|
+
* @param {KeyboardEvent} event - The keyboard event
|
|
153
|
+
* @param {Object} options - Navigation options
|
|
154
|
+
* @param {boolean} options.horizontal - Whether navigation is horizontal
|
|
155
|
+
* @param {boolean} options.nested - Whether navigation is nested
|
|
156
|
+
* @returns {string|null} The determined action or null if no action matches
|
|
157
|
+
*/
|
|
158
|
+
export const getKeyboardAction = (event, options) => {
|
|
159
|
+
const { key, ctrlKey, metaKey } = event
|
|
160
|
+
|
|
161
|
+
// Check for modifier keys first (highest priority)
|
|
162
|
+
if (ctrlKey || metaKey) {
|
|
163
|
+
const modifierMap = createModifierKeyboardActionMap(options)
|
|
164
|
+
return modifierMap[key] || null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get the action map based on options
|
|
168
|
+
const actionMap = createKeyboardActionMap(options)
|
|
169
|
+
|
|
170
|
+
// Return the action or null if no matching key
|
|
171
|
+
return actionMap[key] || null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Determines an action based on a click event
|
|
176
|
+
*
|
|
177
|
+
* @param {MouseEvent} event - The click event
|
|
178
|
+
* @returns {string} The determined action
|
|
179
|
+
*/
|
|
180
|
+
export const getClickAction = (event) => {
|
|
181
|
+
const { ctrlKey, metaKey, target } = event
|
|
182
|
+
|
|
183
|
+
// Check for modifier keys first (highest priority)
|
|
184
|
+
if (ctrlKey || metaKey) {
|
|
185
|
+
return 'extend'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check if clicked on icon with collapsed/expanded state
|
|
189
|
+
if (isNodeToggle(target) || isNodeToggle(target.parentElement)) {
|
|
190
|
+
return 'toggle'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Default action
|
|
194
|
+
return 'select'
|
|
195
|
+
}
|