Skip to content

Commit

Permalink
Merge branch 'main' into list-focus-update
Browse files Browse the repository at this point in the history
  • Loading branch information
kgogov committed May 23, 2024
2 parents fe0844d + d773532 commit 778b966
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 26 deletions.
45 changes: 32 additions & 13 deletions packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ abstract class UI5Element extends HTMLElement {
_inDOM: boolean;
_fullyConnected: boolean;
_childChangeListeners: Map<string, ChildChangeListener>;
_slotsAssignedNodes: WeakMap<HTMLSlotElement, Array<SlotValue>>;
_slotChangeListeners: Map<string, SlotChangeListener>;
_domRefReadyPromise: Promise<void> & { _deferredResolve?: PromiseResolve };
_doNotSyncAttributes: Set<string>;
Expand Down Expand Up @@ -153,6 +154,7 @@ abstract class UI5Element extends HTMLElement {
});
this._domRefReadyPromise._deferredResolve = deferredResolve;
this._doNotSyncAttributes = new Set(); // attributes that are excluded from attributeChangedCallback synchronization
this._slotsAssignedNodes = new WeakMap(); // map of all nodes, slotted (directly or transitively) per component slot

this._state = { ...ctor.getMetadata().getInitialState() };

Expand Down Expand Up @@ -396,15 +398,12 @@ abstract class UI5Element extends HTMLElement {
// Listen for any invalidation on the child if invalidateOnChildChange is true or an object (ignore when false or not set)
if (instanceOfUI5Element(child) && slotData.invalidateOnChildChange) {
const childChangeListener = this._getChildChangeListener(slotName);

if (childChangeListener) {
child.attachInvalidate.call(child, childChangeListener);
}
child.attachInvalidate.call(child, childChangeListener);
}

// Listen for the slotchange event if the child is a slot itself
if (child instanceof HTMLSlotElement) {
this._attachSlotChange(child, slotName);
this._attachSlotChange(child, slotName, !!slotData.invalidateOnChildChange);
}

const propertyName = slotData.propertyName || slotName;
Expand Down Expand Up @@ -465,10 +464,7 @@ abstract class UI5Element extends HTMLElement {
children.forEach(child => {
if (instanceOfUI5Element(child)) {
const childChangeListener = this._getChildChangeListener(slotName);

if (childChangeListener) {
child.detachInvalidate.call(child, childChangeListener);
}
child.detachInvalidate.call(child, childChangeListener);
}

if (child instanceof HTMLSlotElement) {
Expand Down Expand Up @@ -659,11 +655,34 @@ abstract class UI5Element extends HTMLElement {
/**
* @private
*/
_attachSlotChange(child: HTMLSlotElement, slotName: string) {
_attachSlotChange(slot: HTMLSlotElement, slotName: string, invalidateOnChildChange: boolean) {
const slotChangeListener = this._getSlotChangeListener(slotName);
if (slotChangeListener) {
child.addEventListener("slotchange", slotChangeListener);
}
slot.addEventListener("slotchange", (e: Event) => {
slotChangeListener.call(slot, e);

if (invalidateOnChildChange) {
// Detach listeners for UI5 Elements that used to be in this slot
const previousChildren = this._slotsAssignedNodes.get(slot);
if (previousChildren) {
previousChildren.forEach(child => {
if (instanceOfUI5Element(child)) {
const childChangeListener = this._getChildChangeListener(slotName);
child.detachInvalidate.call(child, childChangeListener);
}
});
}

// Attach listeners for UI5 Elements that are now in this slot
const newChildren = getSlottedNodesList([slot]);
this._slotsAssignedNodes.set(slot, newChildren);
newChildren.forEach(child => {
if (instanceOfUI5Element(child)) {
const childChangeListener = this._getChildChangeListener(slotName);
child.attachInvalidate.call(child, childChangeListener);
}
});
}
});
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/compat/src/i18n/messagebundle_cs.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ TABLE_ROW_POSITION={0} z {1}

TABLE_GROUP_ROW_ARIA_LABEL=Řádka hlavičky skupiny

ARIA_LABEL_ROW_SELECTION=Výběr položek
ARIA_LABEL_ROW_SELECTION=Výběr položky

ARIA_LABEL_SELECT_ALL_CHECKBOX=Vybrat všechny řádky

Expand Down
22 changes: 21 additions & 1 deletion packages/main/src/MultiInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,16 @@ class MultiInput extends Input implements IFormInputElement {
return this._focusFirstToken(e);
}

if (isLeft(e) || isBackSpace(e)) {
if (isLeft(e)) {
this._skipOpenSuggestions = true;
return this._handleLeft(e);
}

if (isBackSpace(e)) {
this._skipOpenSuggestions = true;
return this._handleBackspace(e);
}

this._skipOpenSuggestions = false;

if (isShow(e)) {
Expand Down Expand Up @@ -283,6 +288,21 @@ class MultiInput extends Input implements IFormInputElement {
}
}

_handleBackspace(e: KeyboardEvent) {
const cursorPosition = this.getDomRef()!.querySelector(`input`)!.selectionStart;
const selectionEnd = this.getDomRef()!.querySelector(`input`)!.selectionEnd;
const isValueSelected = cursorPosition === 0 && selectionEnd === this.value.length;
const tokens = this.tokens;
const lastToken = tokens.length && tokens[tokens.length - 1];

// selectionStart property applies only to inputs of types text, search, URL, tel, and password
if ((!this.value || (this.value && cursorPosition === 0 && !isValueSelected)) && lastToken) {
e.preventDefault();
lastToken.focus();
this.tokenizer._itemNav.setCurrentItem(lastToken);
}
}

_focusFirstToken(e: KeyboardEvent) {
const tokens = this.tokens;
const firstToken = tokens.length && tokens[0];
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/ToolbarButton.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
id="{{this.id}}"
style="{{this.styles}}"
icon="{{this.icon}}"
?icon-end="{{this.iconEnd}}"
end-icon="{{this.endIcon}}"
tooltip="{{this.tooltip}}"
accessible-name="{{this.accessibleName}}"
accessible-name-ref="{{this.accessibleNameRef}}"
Expand Down
16 changes: 12 additions & 4 deletions packages/main/src/ToolbarButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,20 @@ class ToolbarButton extends ToolbarItem {
icon!: string;

/**
* Defines whether the icon should be displayed after the component text.
* @default false
* Defines the icon, displayed as graphical element within the component after the button text.
*
* **Note:** It is highly recommended to use `endIcon` property only together with `icon` and/or `text` properties.
* Usage of `endIcon` only should be avoided.
*
* The SAP-icons font provides numerous options.
*
* Example:
* See all the available icons within the [Icon Explorer](https://sdk.openui5.org/test-resources/sap/m/demokit/iconExplorer/webapp/index.html).
* @default ""
* @public
*/
@property({ type: Boolean })
iconEnd!: boolean;
@property()
endIcon!: string;

/**
* Defines the tooltip of the component.
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/ToolbarPopoverButton.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<ui5-button
icon="{{this.icon}}"
?icon-end="{{this.iconEnd}}"
end-icon="{{this.endIcon}}"
accessible-name="{{this.accessibleName}}"
accessible-name-ref="{{this.accessibleNameRef}}"
.accessibilityAttributes="{{this.accessibilityAttributes}}"
Expand Down
2 changes: 1 addition & 1 deletion packages/main/test/pages/ToolbarButton.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
design="Emphasized"
disabled
icon="sap-icon://add"
icon-end
end-icon="sap-icon://employee"
tooltip="Add"
></ui5-toolbar-button>

Expand Down
37 changes: 37 additions & 0 deletions packages/main/test/specs/MultiInput.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { assert } from "chai";

const isMacOS = process.platform === 'darwin';
const keyCtrlToPress = isMacOS ? 'Command' : 'Control';

const isListItemFocused = async (listItem) => {
return await browser.execute(el => {
const pseudoElementStyle = window.getComputedStyle(el, ":after");
Expand Down Expand Up @@ -523,6 +526,40 @@ describe("Keyboard handling", () => {
assert.ok(await lastToken.getProperty("focused"), "The last token is focused on Backspace");
});

it("should focus token last token when caret is at the beginning of the value", async () => {
const input = await browser.$("#two-tokens");
const innerInput = await input.shadow$("input");
const lastToken = await browser.$("#two-tokens ui5-token#secondToken");

// Act
await innerInput.click();
await browser.keys("ArrowLeft");
await browser.keys("ArrowLeft");
await browser.keys("ArrowLeft");
await browser.keys("Backspace");

assert.ok(await lastToken.getProperty("focused"), "The last token is focused on Backspace");
});

it("should delete value on backspace", async () => {
const input = await browser.$("#two-tokens");
const innerInput = await input.shadow$("input");
const lastToken = await browser.$("#two-tokens ui5-token#secondToken");

// Act
await innerInput.click();
await browser.keys([keyCtrlToPress, "a"]);
await browser.keys("Backspace");

// Assert
assert.strictEqual(await input.getProperty("value"), "", "Value is deleted on Backspace");

await browser.keys("Backspace");

assert.notOk(await input.getProperty("focused"), "The input loses focus on Backspace");
assert.ok(await lastToken.getProperty("focused"), "The last token is focused on Backspace");
});

it("should delete token on backspace", async () => {
const input = await browser.$("#two-tokens");
const innerInput = await input.shadow$("input");
Expand Down
8 changes: 4 additions & 4 deletions packages/main/test/specs/ToolbarButton.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ describe("Toolbar general interaction", () => {
const buttonDesign = await button.getAttribute("design");
const buttonDisabled = await button.getAttribute("disabled");
const buttonIcon = await button.getAttribute("icon");
const buttonIconEnd = await button.getAttribute("icon-end");
const buttonEndIcon = await button.getAttribute("end-icon");
const buttonTooltip = await button.getAttribute("tooltip");

assert.strictEqual(buttonText, "Back", "Button text is correct");
assert.strictEqual(buttonDesign, "Emphasized", "Button design is correct");
assert.strictEqual(buttonDisabled, "true", "Button is disabled");
assert.strictEqual(buttonIcon, "sap-icon://add", "Button icon is correct");
assert.strictEqual(buttonIconEnd, "", "Button icon-end is correct");
assert.strictEqual(buttonEndIcon, "sap-icon://employee", "Button end-icon is correct");
assert.strictEqual(buttonTooltip, "Add", "Button tooltip is correct");
});

Expand Down Expand Up @@ -52,14 +52,14 @@ describe("Toolbar general interaction", () => {
const buttonDesign = await popover.$("ui5-button").getAttribute("design");
const buttonDisabled = await popover.$("ui5-button").getAttribute("disabled");
const buttonIcon = await popover.$("ui5-button").getAttribute("icon");
const buttonIconEnd = await popover.$("ui5-button").getAttribute("icon-end");
const buttonEndIcon = await popover.$("ui5-button").getAttribute("end-icon");
const buttonTooltip = await popover.$("ui5-button").getAttribute("tooltip");

assert.strictEqual(buttonText, "Back", "Button's text is correct inside popover");
assert.strictEqual(buttonDesign, "Emphasized", "Button's design is correct inside popover");
assert.strictEqual(buttonDisabled, "true", "Button is disabled inside popover");
assert.strictEqual(buttonIcon, "sap-icon://add", "Button's icon is correct inside popover");
assert.strictEqual(buttonIconEnd, "", "Button's icon-end is correct inside popover");
assert.strictEqual(buttonEndIcon, "sap-icon://employee", "Button's end-icon is correct inside popover");
assert.strictEqual(buttonTooltip, "Add", "Button's tooltip is correct inside popover");
});

Expand Down

0 comments on commit 778b966

Please sign in to comment.