@rokkit/actions 1.0.0-next.108 → 1.0.0-next.110

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/actions",
3
- "version": "1.0.0-next.108",
3
+ "version": "1.0.0-next.110",
4
4
  "description": "Contains generic actions that can be used in various components.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -1,260 +0,0 @@
1
- import { on } from 'svelte/events'
2
- import { getClosestAncestorWithAttribute, getEventForKey } from './utils.js'
3
- import { getPathFromKey } from '@rokkit/core'
4
-
5
- /**
6
- * Key mappings for different navigation directions
7
- */
8
- const KEY_MAPPINGS = {
9
- horizontal: {
10
- prev: ['ArrowLeft'],
11
- next: ['ArrowRight'],
12
- collapse: ['ArrowUp'],
13
- expand: ['ArrowDown'],
14
- select: ['Enter'],
15
- toggle: ['Space'],
16
- delete: ['Delete', 'Backspace']
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
- }
27
- }
28
-
29
- /**
30
- * Navigator class to handle keyboard and mouse navigation
31
- * @class
32
- */
33
- class NavigatorController {
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)
150
-
151
- // Handle selection based on modifier keys
152
- if (this.hasModifierKey(event) && this.wrapper.multiSelect) {
153
- this.executeAction('extend', path)
154
- } else {
155
- this.executeAction('select', path)
156
- }
157
-
158
- // this.executeAction('toggle', path)
159
- event.stopPropagation()
160
- }
161
-
162
- /**
163
- * Handle clicks on toggle icons
164
- * @param {number[]} path - The path of the item to perform the action on
165
- * @param {HTMLElement} target - The clicked element
166
- * @returns {boolean} - Whether a toggle icon was clicked and handled
167
- */
168
- handleToggleIconClick(path, target) {
169
- const isIcon = target.tagName.toLowerCase() === 'rk-icon'
170
- const state = target.getAttribute('data-state')
171
- if (!isIcon || !['closed', 'opened'].includes(state)) return false
172
-
173
- return this.executeAction('toggle', path)
174
- }
175
-
176
- /**
177
- * Execute an action if available
178
- * @param {string} actionName - The name of the action to execute
179
- * @param {number[]} path - The path of the item to perform the action on
180
- * @returns {boolean} - Whether the action was executed
181
- */
182
- executeAction(actionName, path) {
183
- // Get the action function based on action name
184
- const action = this.getActionFunction(actionName)
185
-
186
- if (action) {
187
- const executed = path ? action(path) : action()
188
- if (executed) this.emitActionEvent(actionName)
189
-
190
- return executed
191
- }
192
- return false
193
- }
194
-
195
- /**
196
- * Get the appropriate action function based on action name and conditions
197
- * @param {string} actionName - The name of the action
198
- * @returns {Function|null} - The action function or null if not available
199
- */
200
- getActionFunction(actionName) {
201
- // Basic navigation and selection actions always available
202
- const actions = {
203
- prev: () => this.wrapper.movePrev(),
204
- next: () => this.wrapper.moveNext(),
205
- select: (path) => this.wrapper.select(path),
206
- collapse: (path) => this.wrapper.collapse(path),
207
- expand: (path) => this.wrapper.expand(path),
208
- toggle: (path) => this.wrapper.toggleExpansion(path),
209
- extend: (path) => this.wrapper.extendSelection(path),
210
- delete: () => this.wrapper.delete()
211
- }
212
-
213
- return actions[actionName] || null
214
- }
215
-
216
- /**
217
- * Emit an action event
218
- * @param {string} eventName - The name of the event to emit
219
- */
220
- emitActionEvent(eventName) {
221
- const data = {
222
- path: this.wrapper.currentNode?.path,
223
- value: this.wrapper.currentNode?.value
224
- }
225
-
226
- // For select events, include selected nodes
227
- if (['select', 'extend'].includes(eventName)) {
228
- data.selected = Array.from(this.wrapper.selectedNodes.values()).map((node) => node.value)
229
- // Normalize selection events to 'select'
230
- eventName = 'select'
231
- } else if (['prev', 'next'].includes(eventName)) {
232
- eventName = 'move'
233
- }
234
-
235
- this.root.dispatchEvent(
236
- new CustomEvent('action', {
237
- detail: {
238
- eventName,
239
- data
240
- }
241
- })
242
- )
243
- }
244
- }
245
-
246
- /**
247
- * Navigator action for Svelte components
248
- * @param {HTMLElement} root - The root element
249
- * @param {Object} params - Parameters including wrapper and options
250
- */
251
- export function navigator(root, { wrapper, options }) {
252
- const controller = new NavigatorController(root, wrapper, options)
253
-
254
- $effect(() => {
255
- const cleanupFunctions = controller.init()
256
- return () => {
257
- cleanupFunctions.forEach((cleanup) => cleanup())
258
- }
259
- })
260
- }