diff --git a/packages/survey-angular-ui/src/components/action-bar/action-bar-item.component.html b/packages/survey-angular-ui/src/components/action-bar/action-bar-item.component.html index 5af4028d99..ebc6de1db2 100644 --- a/packages/survey-angular-ui/src/components/action-bar/action-bar-item.component.html +++ b/packages/survey-angular-ui/src/components/action-bar/action-bar-item.component.html @@ -1,5 +1,5 @@ - diff --git a/packages/survey-vue3-ui/src/components/action-bar/ActionBarItem.vue b/packages/survey-vue3-ui/src/components/action-bar/ActionBarItem.vue index 694b1a4c6d..6507cf4571 100644 --- a/packages/survey-vue3-ui/src/components/action-bar/ActionBarItem.vue +++ b/packages/survey-vue3-ui/src/components/action-bar/ActionBarItem.vue @@ -12,6 +12,16 @@ evt.stopPropagation(); } " + v-on:mousedown=" + () => { + item.doMouseDown(); + } + " + v-on:focus=" + (event) => { + item.doFocus(event); + } + " v-bind:disabled="item.disabled" v-bind:title="item.tooltip || item.title" v-bind:aria-checked="item.ariaChecked" diff --git a/src/actions/action.ts b/src/actions/action.ts index 87697c28a4..cc849d20b2 100644 --- a/src/actions/action.ts +++ b/src/actions/action.ts @@ -63,6 +63,7 @@ export interface IAction { * [View Demo](https://surveyjs.io/form-library/examples/add-custom-navigation-button/ (linkStyle)) */ action?: (context?: any) => void; + onFocus?: (isMouse: boolean, event: any) => void; /** * One or several CSS classes that you want to apply to the outer `
` element. * @@ -463,6 +464,7 @@ export class Action extends BaseAction implements IAction, ILocalizableOwner { }) locTooltipName?: string; @property() private _enabled: boolean; @property() action: (context?: any, isUserAction?: boolean) => void; + @property() onFocus: (isMouse: boolean, event: any) => void; @property() _component: string; @property() items: any; @property({ @@ -509,6 +511,17 @@ export class Action extends BaseAction implements IAction, ILocalizableOwner { evt.stopPropagation(); return true; } + private isMouseDown: boolean; + public doMouseDown() : void { + this.isMouseDown = true; + } + public doFocus(args: any): void { + if(!!this.onFocus) { + const evt = !!args.originalEvent ? args.originalEvent : args; + this.onFocus(this.isMouseDown, evt); + } + this.isMouseDown = false; + } private locStrChangedInPopupModel(): void { if (!this.popupModel || !this.popupModel.contentComponentData || !this.popupModel.contentComponentData.model) return; const model = this.popupModel.contentComponentData.model; diff --git a/src/knockout/components/action-bar/action-bar-item.ts b/src/knockout/components/action-bar/action-bar-item.ts index c074764bfa..2c6fbd035d 100644 --- a/src/knockout/components/action-bar/action-bar-item.ts +++ b/src/knockout/components/action-bar/action-bar-item.ts @@ -4,7 +4,14 @@ export let ActionBarItemViewModel: any; ko.components.register("sv-action-bar-item", { viewModel: { - createViewModel: (params: any) => { + createViewModel: (params: any, componentInfo: any) => { + let el = componentInfo.element; + el = !!el.nextElementSibling ? el.nextElementSibling : el.parentElement.firstElementChild; + if(!!el) { + const item = params.item; + el.onfocus = function (args: any) { item.doFocus(args); }; + el.onmousedown = function () { item.doMouseDown(); }; + } return params; }, }, diff --git a/src/react/components/action-bar/action-bar-item.tsx b/src/react/components/action-bar/action-bar-item.tsx index 56cdbd82f0..04edb0b16c 100644 --- a/src/react/components/action-bar/action-bar-item.tsx +++ b/src/react/components/action-bar/action-bar-item.tsx @@ -91,6 +91,8 @@ export class SurveyActionBarItem extends SurveyElementBase< className={className} type="button" disabled={this.item.disabled} + onMouseDown={() => this.item.doMouseDown()} + onFocus={(args) => this.item.doFocus(args)} onClick={(args) => this.item.doAction(args)} title={title} tabIndex={tabIndex} diff --git a/src/vue/components/action-bar/action-bar-item.vue b/src/vue/components/action-bar/action-bar-item.vue index cb0d738b2b..e5f61b40b5 100644 --- a/src/vue/components/action-bar/action-bar-item.vue +++ b/src/vue/components/action-bar/action-bar-item.vue @@ -12,6 +12,12 @@ evt.stopPropagation(); } " + v-on:mousedown=" + () => { item.doMouseDown(); } + " + v-on:focus=" + (event) => { item.doFocus(event); } + " v-bind:disabled="item.disabled" v-bind:title="item.tooltip || item.title" v-bind:aria-checked="item.ariaChecked" diff --git a/testCafe/survey/titleActions.js b/testCafe/survey/titleActions.js index 15429f7f61..733382179f 100644 --- a/testCafe/survey/titleActions.js +++ b/testCafe/survey/titleActions.js @@ -361,3 +361,42 @@ frameworks.forEach((framework) => { })()).gt(0); }); }); + +function addTitleActionForFocus(_, opt) { + opt.titleActions = [{ + title: "main_action", + action: () => {}, + onFocus: (isMouse, event) => { + opt.question.value = isMouse ? "focus_mouse" : "focus_keyboard"; + event.stopPropagation(); + } + }]; +} + +frameworks.forEach(async framework => { + fixture`${framework} ${title}` + .page`${url_test}${themeName}/${framework}`.beforeEach(async (t) => { + await applyTheme(themeName); + }); + + test("Check Action focus", async t => { + await initSurvey(framework, { + elements: [ + { + type: "text", + name: "q1" + } + ] + }, { onGetQuestionTitleActions: addTitleActionForFocus }); + const action = Selector(".sv-action"); + + await t + .wait(1000) + .click(action) + .expect(Selector("input").value).eql("focus_mouse") + .pressKey("tab") + .expect(Selector("input").value).eql("focus_mouse") + .pressKey("shift+tab") + .expect(Selector("input").value).eql("focus_keyboard"); + }); +});