Skip to content

Commit 2113502

Browse files
committed
shortcuts added and validated
1 parent 542ad2c commit 2113502

File tree

11 files changed

+77
-30
lines changed

11 files changed

+77
-30
lines changed

administrator/components/com_workflow/resources/scripts/components/canvas/ControlsPanel.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<Panel position="top-left" aria-label="Workflow actions" class="d-flex gap-2 p-2">
33
<button
44
@click="$emit('add-stage')"
5-
class="btn btn-primary d-flex align-items-center gap-1"
5+
class="toolbar-button btn btn-primary d-flex align-items-center gap-1"
6+
tabindex="0"
67
:aria-label="translate('COM_WORKFLOW_GRAPH_ADD_STAGE')"
78
>
89
<span class="icon icon-plus" aria-hidden="true"></span>
@@ -11,7 +12,8 @@
1112

1213
<button
1314
@click="$emit('add-transition')"
14-
class="btn btn-info d-flex align-items-center gap-1"
15+
class="toolbar-button btn btn-info d-flex align-items-center gap-1"
16+
tabindex="0"
1517
:aria-label="translate('COM_WORKFLOW_GRAPH_ADD_TRANSITION')"
1618
>
1719
<span class="icon icon-plus" aria-hidden="true"></span>
@@ -20,7 +22,8 @@
2022

2123
<button
2224
@click="$emit('toggle-transition-mode')"
23-
:class="['btn', isTransitionMode ? 'btn-success' : 'btn-primary', 'd-flex', 'align-items-center', 'gap-1']"
25+
:class="['btn toolbar-button', isTransitionMode ? 'btn-success' : 'btn-primary', 'd-flex', 'align-items-center', 'gap-1']"
26+
tabindex="0"
2427
:aria-pressed="isTransitionMode"
2528
:aria-label="isTransitionMode ? translate('COM_WORKFLOW_GRAPH_EXIT_TRANSITION_MODE') : translate('COM_WORKFLOW_GRAPH_ENTER_TRANSITION_MODE')"
2629
>

administrator/components/com_workflow/resources/scripts/components/canvas/CustomControls.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
<template>
22
<div class="custom-controls" tabindex="0" ref="controlsContainer">
33
<button
4-
class="custom-controls-button"
4+
class="toolbar-button custom-controls-button"
5+
tabindex="0"
56
@click="zoomIn"
67
aria-label="Zoom in"
78
title="Zoom in (+ key)"
89
>
910
<i class="icon icon-plus"></i>
1011
</button>
1112
<button
12-
class="custom-controls-button"
13+
class="toolbar-button custom-controls-button"
14+
tabindex="0"
1315
@click="zoomOut"
1416
aria-label="Zoom out"
1517
title="Zoom out (- key)"
1618
>
1719
<i class="icon icon-minus"></i>
1820
</button>
1921
<button
20-
class="custom-controls-button"
22+
class="toolbar-button custom-controls-button"
23+
tabindex="0"
2124
@click="fitView"
2225
aria-label="Fit view"
2326
title="Fit view (F key)"

administrator/components/com_workflow/resources/scripts/components/canvas/WorkflowCanvas.vue

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ import stageNode from '../nodes/StageNode.vue';
5959
import customEdge from '../edges/CustomEdge.vue';
6060
import CustomControls from './CustomControls.vue';
6161
import ControlsPanel from './ControlsPanel.vue';
62-
import { announce, setupDialogFocusHandlers } from '../utils/focus-utils.js';
63-
import { generatePositionedNodes, createSpecialNode } from '../utils/positioning.js';
64-
import { generateStyledEdges } from '../utils/edges.js';
65-
import { setupGlobalShortcuts } from '../utils/KeyboardManager.js';
66-
import { debounce } from '../utils/utils.es6'
62+
import { announce, setupDialogFocusHandlers } from '../../utils/focus-utils.js';
63+
import { generatePositionedNodes, createSpecialNode } from '../../utils/positioning.js';
64+
import { generateStyledEdges } from '../../utils/edges.js';
65+
import { setupGlobalShortcuts } from '../../utils/KeyboardManager.js';
66+
import { debounce } from '../../utils/utils.es6'
6767
6868
export default {
6969
name: 'WorkflowCanvas',
@@ -95,6 +95,7 @@ export default {
9595
const selectedTransition = ref(null);
9696
const liveRegion = ref(null);
9797
const saveStatus = ref('upToDate');
98+
const currentFocusMode = ref('stages');
9899
const previouslyFocusedElement = ref(null);
99100
100101
const stages = computed(() => store.getters.stages || []);
@@ -126,6 +127,7 @@ export default {
126127
...edge,
127128
data: {
128129
...edge.data,
130+
onSelect: () => selectEdge(edge.id),
129131
onDelete: () => showDeleteModal('transition', edge.id),
130132
onEdit: () => editTransition(edge.id)
131133
}
@@ -189,7 +191,7 @@ export default {
189191
selectedTransition.value = null;
190192
}
191193
function handleConnect() { if (isTransitionMode.value) openModal('transition'); }
192-
function selectEdge({ edge }) { selectTransition(edge.id); }
194+
function selectEdge(id) { selectTransition(id); }
193195
function updateSaveMessage() {
194196
const el = document.getElementById('save-message');
195197
if (!el) return;
@@ -271,6 +273,7 @@ export default {
271273
selectedStage,
272274
selectedTransition,
273275
isTransitionMode,
276+
currentFocusMode,
274277
liveRegion: liveRegion.value
275278
},
276279
setSaveStatus: (val) => saveStatus.value = val,

administrator/components/com_workflow/resources/scripts/components/edges/CustomEdge.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ export default {
9494
methods: {
9595
onEdgeKeydown(e) {
9696
if (e.key === 'Enter' || e.key === ' ') {
97-
this.data.onEdit?.();
97+
this.data.onSelect();
9898
e.preventDefault();
9999
}
100-
if (e.key === 'Delete' || e.key === 'Backspace') {
100+
if ((e.key === 'e' || e.key === 'E') && this.data?.isTransitionMode) {
101+
this.data.onEdit();
102+
e.preventDefault();
103+
}
104+
if ((e.key === 'Delete' || e.key === 'Backspace') && this.data?.isTransitionMode) {
101105
this.data.onDelete?.();
102106
e.preventDefault();
103107
}

administrator/components/com_workflow/resources/scripts/components/nodes/StageNode.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676

7777
<script>
7878
import { Handle, Position } from '@vue-flow/core';
79-
import {focusNode} from "../utils/focus-utils";
79+
import {focusNode} from "../../utils/focus-utils";
8080
8181
export default {
8282
name: 'StageNode',

administrator/components/com_workflow/resources/scripts/utils/KeyboardManager.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { announce, cycleFocus } from './focus-utils';
1+
import { announce, cycleFocus, cycleMode } from './focus-utils';
22

33
/**
44
* Attach global keyboard listeners for workflow canvas.
@@ -10,9 +10,11 @@ import { announce, cycleFocus } from './focus-utils';
1010
* @param {Function} toggleMode
1111
* @param {Function} undo
1212
* @param {Function} redo
13+
*
1314
* @param {Function} clearSelection
1415
* @param {Function} zoomIn
1516
* @param {Function} zoomOut
17+
*
1618
* @param {Object} state - { selectedStage, selectedTransition, isTransitionMode, liveRegion }
1719
*/
1820
export function setupGlobalShortcuts({ addStage, addTransition, editItem, deleteItem,
@@ -94,21 +96,35 @@ export function setupGlobalShortcuts({ addStage, addTransition, editItem, delete
9496

9597
case e.key === 'Tab':
9698
e.preventDefault();
97-
cycleFocus('.stage-node[tabindex="0"], .edge-label[tabindex="0"]', e.shiftKey);
99+
cycleMode(['stages', 'transitions', 'toolbar', 'actions', 'links', 'buttons'], state.currentFocusMode, state.liveRegion);
98100
break;
99101

100102
case ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key):
103+
e.preventDefault();
101104
if (state.selectedStage.value) {
102-
e.preventDefault();
103105
moveNode(state.selectedStage.value.toString(), e.key, e.shiftKey);
104-
}else{
106+
}else if(e.shiftKey){
105107
const panStep = 20;
106108
switch (e.key) {
107109
case 'ArrowUp': viewport.value.y += panStep; break;
108110
case 'ArrowDown': viewport.value.y -= panStep; break;
109111
case 'ArrowLeft': viewport.value.x += panStep; break;
110112
case 'ArrowRight': viewport.value.x -= panStep; break;
111113
}
114+
}else{
115+
const reverse = ['ArrowLeft', 'ArrowUp'].includes(e.key);
116+
const groupSelectors = {
117+
stages: '.stage-node[tabindex="0"]',
118+
transitions: '.edge-label[tabindex="0"]',
119+
toolbar: '.toolbar-button[tabindex="0"]',
120+
actions: '.action-button[tabindex="0"]',
121+
links: 'a[href][tabindex="0"], a[href]:not([tabindex="-1"])',
122+
buttons: 'button[tabindex="0"], button:not([tabindex="-1"])'
123+
};
124+
const selector = groupSelectors[state.currentFocusMode.value];
125+
if (selector) {
126+
cycleFocus(selector, reverse);
127+
} break;
112128
}
113129
break;
114130
}

administrator/components/com_workflow/resources/scripts/utils/edges.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getEdgeColor} from "./utils.es6";
1+
import { getEdgeColor} from "./utils.es6.js";
22

33
/**
44
* Generate styled edges based on transition data.

administrator/components/com_workflow/resources/scripts/utils/focus-utils.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ export function cycleFocus(selector, reverse = false) {
4747
elements[nextIndex].focus();
4848
}
4949

50+
/**
51+
* Cycle between defined focus modes (e.g., stages → transitions → toolbar → actions).
52+
* @param {string[]} focusModes - Array of focus mode strings.
53+
* @param {Ref<string>} currentModeRef - Vue ref holding the current mode.
54+
* @param {HTMLElement} liveRegionElement - ARIA live region for screen reader feedback.
55+
*/
56+
export function cycleMode(focusModes, currentModeRef, liveRegionElement) {
57+
const currentIndex = focusModes.indexOf(currentModeRef.value);
58+
const nextIndex = (currentIndex + 1) % focusModes.length;
59+
currentModeRef.value = focusModes[nextIndex];
60+
announce(liveRegionElement, `Focus mode: ${focusModes[nextIndex]}`);
61+
}
62+
63+
5064
/**
5165
*
5266
*

administrator/components/com_workflow/resources/scripts/utils/positioning.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getColorForStage } from "./utils.es6"
1+
import { getColorForStage } from "./utils.es6.js"
22

33
/**
44
* Calculate and return positioned stage nodes in a grid layout.

administrator/components/com_workflow/src/View/Graph/HtmlView.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,20 +163,20 @@ protected function addToolbar()
163163
if ($itemEditable){
164164
$toolbar->customButton('undo')
165165
->html('<joomla-toolbar-button><button onclick="WorkflowGraph.Event.fire(\'onClickUndoWorkflow\')" '
166-
. 'class="btn btn-info"><span class="icon-undo-2 icon-fw" aria-hidden="true"></span>'
166+
. 'class="btn btn-info action-button" tabindex="0"><span class="icon-undo-2 icon-fw" aria-hidden="true"></span>'
167167
. Text::_('COM_WORKFLOW_UNDO') . '</button></joomla-toolbar-button>');
168168

169169
$toolbar->customButton('redo')
170170
->html('<joomla-toolbar-button><button onclick="WorkflowGraph.Event.fire(\'onClickRedoWorkflow\')" '
171-
. 'class="btn btn-info"><span class="icon-redo icon-fw" aria-hidden="true"></span>'
171+
. 'class="btn btn-info action-button" tabindex="0"><span class="icon-redo icon-fw" aria-hidden="true"></span>'
172172
. Text::_('COM_WORKFLOW_REDO') . '</button></joomla-toolbar-button>');
173173

174174

175175

176176
$toolbar->help('Workflow');
177177
$toolbar->customButton('Shortcuts')
178-
->html('<joomla-toolbar-button><button class="btn btn-info" data-joomla-dialog="'
179-
. htmlspecialchars($shortcutsPopupOptions, ENT_QUOTES, 'UTF-8') . '"'
178+
->html('<joomla-toolbar-button><button class="btn btn-info action-button" data-joomla-dialog="'
179+
. htmlspecialchars($shortcutsPopupOptions, ENT_QUOTES, 'UTF-8') . '" tabindex="0"'
180180
. 'title="' . Text::_('COM_WORKFLOW_GRAPH_SHORTCUTS') . '"><span class="fa fa-keyboard" aria-hidden="true"></span>'
181181
. Text::_('COM_WORKFLOW_GRAPH_SHORTCUTS') . '</button></joomla-toolbar-button>');
182182
}

0 commit comments

Comments
 (0)