Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-switch): add required property #7324

Merged
merged 11 commits into from
Jul 26, 2023
2 changes: 2 additions & 0 deletions packages/main/src/Switch.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
aria-label="{{ariaLabelText}}"
aria-checked="{{checked}}"
aria-disabled="{{effectiveAriaDisabled}}"
aria-required="{{required}}"
@click="{{_onclick}}"
@keyup="{{_onkeyup}}"
@keydown="{{_onkeydown}}"
Expand Down Expand Up @@ -47,4 +48,5 @@
</div>

<input type='checkbox' ?checked="{{checked}}" class="ui5-switch-input" data-sap-no-tab-ref/>
<slot name="formSupport"></slot>
</div>
67 changes: 66 additions & 1 deletion packages/main/src/Switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";

import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
Expand All @@ -13,6 +14,9 @@ import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/Ari
import "@ui5/webcomponents-icons/dist/accept.js";
import "@ui5/webcomponents-icons/dist/decline.js";
import "@ui5/webcomponents-icons/dist/less.js";
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
import type FormSupport from "./features/InputElementsFormSupport.js";
import type { IFormElement } from "./features/InputElementsFormSupport.js";
import Icon from "./Icon.js";
import SwitchDesign from "./types/SwitchDesign.js";

Expand Down Expand Up @@ -77,7 +81,7 @@ import switchCss from "./generated/themes/Switch.css.js";
* @event sap.ui.webc.main.Switch#change
*/
@event("change")
class Switch extends UI5Element {
class Switch extends UI5Element implements IFormElement {
/**
* Defines the component design.
* <br><br>
Expand Down Expand Up @@ -188,8 +192,69 @@ class Switch extends UI5Element {
@property()
tooltip!: string;

/**
* Defines whether the component is required.
*
* @type {boolean}
* @name sap.ui.webc.main.Switch.prototype.required
* @defaultvalue false
* @public
* @since 1.16.0
*/
@property({ type: Boolean })
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
required!: boolean;

/**
* Determines the name with which the component will be submitted in an HTML form.
*
* <br><br>
* <b>Important:</b> For the <code>name</code> property to have effect, you must add the following import to your project:
* <code>import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";</code>
*
* <br><br>
* <b>Note:</b> When set, a native <code>input</code> HTML element
* will be created inside the component so that it can be submitted as
* part of an HTML form. Do not use this property unless you need to submit a form.
*
* @type {string}
* @name sap.ui.webc.main.Switch.prototype.name
* @defaultvalue ""
* @public
* @since 1.16.0
*/
@property()
name!: string;

/**
* The slot is used to render native <code>input</code> HTML element within Light DOM to enable form submit, when <code>Switch</code> is a part of HTML form.
*
* @type {HTMLElement[]}
* @slot
* @private
* @since 1.16.0
*/
@slot()
formSupport!: Array<HTMLElement>;

static i18nBundle: I18nBundle;

onBeforeRendering() {
this._enableFormSupport();
}

_enableFormSupport() {
const formSupport = getFeature<typeof FormSupport>("FormSupport");
if (formSupport) {
formSupport.syncNativeHiddenInput(this, (element: IFormElement, nativeInput: HTMLInputElement) => {
const switchComponent = (element as Switch);
nativeInput.checked = !!switchComponent.checked;
nativeInput.disabled = !!switchComponent.disabled || switchComponent.checked;
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
});
} else if (this.name) {
console.warn(`In order for the "name" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line
}
}

get sapNextIcon() {
return this.checked ? "accept" : "less";
}
Expand Down
14 changes: 14 additions & 0 deletions packages/main/test/pages/Switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ <h3>Default Switch</h3>
</div>
<ui5-label id="lbl"></ui5-label>

<h3>Switch with required attribute</h3>
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
<div>
<ui5-switch id="requiredSwitch" required text-on="On" text-off="Off"></ui5-switch>
</div>

<h3>Change prevented Switch</h3>
<div class="switch2auto">
<ui5-switch id="switchprevented" text-on="On" text-off="Off"></ui5-switch>
Expand All @@ -55,6 +60,15 @@ <h3>Graphical Switch</h3>
<ui5-switch design="Graphical" checked text-on="Yes" text-off="No" disabled></ui5-switch>
</div>

<h3>Switch in form, required</h3>
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
<div class="switch2auto">
<form id="myForm">
<ui5-switch name="switch" text-on="On" text-off="Off" required></ui5-switch>
<br />
<ui5-button type="Submit">Submit Form</ui5-button>
</form>
</div>

<h3>Custom Switch</h3>
<div class="switch2auto">
<ui5-switch text-on="Accept" text-off="Reject" class="switch4auto"></ui5-switch>
Expand Down
5 changes: 5 additions & 0 deletions packages/main/test/specs/Switch.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ describe("Switch general interaction", async () => {
assert.strictEqual(await switchPrevented.getProperty("checked"), currentChecked, "The switch is not checked");
});

it("The 'required' attribute is propagated properly", async () => {
assert.strictEqual(await browser.$("#requiredSwitch").shadow$(".ui5-switch-root").getAttribute("aria-required"), "true", "The required attribute is set correctly");
assert.strictEqual(await browser.$("#switchprevented").shadow$(".ui5-switch-root").getAttribute("aria-required"), "false", "The required attribute is set correctly");
})

});
27 changes: 26 additions & 1 deletion packages/playground/_stories/main/Switch/Switch.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const Template: UI5StoryArgs<Switch, StoryArgsSlots> = (args) => html`<ui5-switc
design="${ifDefined(args.design)}"
?checked="${ifDefined(args.checked)}"
?disabled="${ifDefined(args.disabled)}"
?required="${ifDefined(args.required)}"
?name="${ifDefined(args.name)}"
text-on="${ifDefined(args.textOn)}"
text-off="${ifDefined(args.textOff)}"
accessible-name="${ifDefined(args.accessibleName)}"
Expand Down Expand Up @@ -60,4 +62,27 @@ export const Design = Template.bind({});
Design.args = {
design: SwitchDesign.Graphical,
accessibleName: "graphical",
};
};

export const RequiredInForm = Template.bind({});
RequiredInForm.args = {
required: true,
name: "mySwitch",
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
disabled: false,
design: SwitchDesign.Textual,
};
RequiredInForm.decorators = [
(story) => {
return html`
<form id="myForm">
<div style="width: fit-content; display: flex; flex-direction: column; justify-content: flex-start">
<div style="display: flex; flex-direction: column; justify-content: flex-start;">
<ui5-label for="mySwitch">The switch is required, and should be checked in order the form to be submit</ui5-label>
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
${story()}
</div>
<ui5-button type="Submit" style="width: fit-content">Submit</ui5-button>
</div>
</form>`
}
];