From a2dbdabcbcb5577d5d3632a9b2ebde47666d8e5e Mon Sep 17 00:00:00 2001 From: mimshins Date: Wed, 5 Mar 2025 17:16:26 +0330 Subject: [PATCH 1/3] fix(web-components/inputs): add explicit `autofocus` property --- .../src/base-input/base-input.ts | 8 ++++++++ .../src/button/base/base-button.ts | 19 +++++++++++++++++++ .../web-components/src/pin-input/pin-input.ts | 8 ++++++++ .../web-components/src/pinwheel/pinwheel.ts | 14 ++++++++++++++ .../web-components/src/text-area/text-area.ts | 1 - .../src/text-field/text-field.ts | 1 - 6 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/web-components/src/base-input/base-input.ts b/packages/web-components/src/base-input/base-input.ts index 082fb890..0d507314 100644 --- a/packages/web-components/src/base-input/base-input.ts +++ b/packages/web-components/src/base-input/base-input.ts @@ -65,6 +65,14 @@ export abstract class BaseInput extends BaseClass { @property({ type: String }) public labelledBy = ""; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + constructor() { super(); diff --git a/packages/web-components/src/button/base/base-button.ts b/packages/web-components/src/button/base/base-button.ts index 8e566870..64669a95 100644 --- a/packages/web-components/src/button/base/base-button.ts +++ b/packages/web-components/src/button/base/base-button.ts @@ -14,6 +14,7 @@ import { type FormSubmitterType, internals, isFocusable, + runAfterRepaint, setupFormSubmitter, withElementInternals, withFocusable, @@ -68,6 +69,14 @@ export abstract class BaseButton extends BaseClass implements FormSubmitter { @property() public size: "sm" | "md" | "lg" = "md"; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + /** * The variant style of the button. * @default "primary" @@ -98,6 +107,16 @@ export abstract class BaseButton extends BaseClass implements FormSubmitter { this._updateFocusability(); } + protected override firstUpdated(changed: PropertyValues): void { + super.firstUpdated(changed); + + runAfterRepaint(() => { + if (!this.autofocus) return; + + this.focus(); + }); + } + protected override updated(changed: PropertyValues) { super.updated(changed); diff --git a/packages/web-components/src/pin-input/pin-input.ts b/packages/web-components/src/pin-input/pin-input.ts index 735b3f4f..30520930 100644 --- a/packages/web-components/src/pin-input/pin-input.ts +++ b/packages/web-components/src/pin-input/pin-input.ts @@ -100,6 +100,14 @@ export class PinInput extends BaseClass { @property({ type: String }) public autocomplete: AutoFill = ""; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + /** * Conveys additional information below the input, such as how it should * be used. diff --git a/packages/web-components/src/pinwheel/pinwheel.ts b/packages/web-components/src/pinwheel/pinwheel.ts index 62e7d362..042bd076 100644 --- a/packages/web-components/src/pinwheel/pinwheel.ts +++ b/packages/web-components/src/pinwheel/pinwheel.ts @@ -60,6 +60,14 @@ export class Pinwheel extends BaseClass { @property({ type: String }) public labelledBy = ""; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + /** * The value of the currently selected item. */ @@ -134,6 +142,12 @@ export class Pinwheel extends BaseClass { "warning", ); } + + runAfterRepaint(() => { + if (!this.autofocus) return; + + this.focus(); + }); } protected override updated(changed: PropertyValues) { diff --git a/packages/web-components/src/text-area/text-area.ts b/packages/web-components/src/text-area/text-area.ts index 722a07d8..39214cfe 100644 --- a/packages/web-components/src/text-area/text-area.ts +++ b/packages/web-components/src/text-area/text-area.ts @@ -72,7 +72,6 @@ export class TextArea extends BaseTextInput { aria-describedby=${ariaDescribedBy} ?required=${!!this.required} ?disabled=${this.disabled} - ?autofocus=${this.autofocus} ?readonly=${this.readOnly} cols=${this.cols || nothing} rows=${this.rows || nothing} diff --git a/packages/web-components/src/text-field/text-field.ts b/packages/web-components/src/text-field/text-field.ts index c7fad244..f8d9010e 100644 --- a/packages/web-components/src/text-field/text-field.ts +++ b/packages/web-components/src/text-field/text-field.ts @@ -196,7 +196,6 @@ export class TextField extends BaseTextInput { aria-describedby=${ariaDescribedBy} ?required=${!!this.required} ?disabled=${this.disabled} - ?autofocus=${this.autofocus} ?readonly=${this.readOnly} inputmode=${this.inputMode || nothing} placeholder=${this.placeholder || nothing} From 58b1a7ffff04c8055208393823cf97c4f4296ad0 Mon Sep 17 00:00:00 2001 From: mimshins Date: Wed, 5 Mar 2025 17:37:58 +0330 Subject: [PATCH 2/3] add documentation --- .../src/button/icon-button/index.ts | 4 ++ .../src/button/standard/index.ts | 4 ++ packages/web-components/src/checkbox/index.ts | 4 ++ .../src/file-input/file-input.ts | 40 ++++++++++++++++++- .../web-components/src/file-input/index.ts | 4 ++ .../web-components/src/pin-input/index.ts | 4 ++ packages/web-components/src/pinwheel/index.ts | 4 ++ packages/web-components/src/radio/index.ts | 4 ++ .../web-components/src/rate-slider/index.ts | 4 ++ .../src/rate-slider/rate-slider.ts | 21 +++++++++- packages/web-components/src/stepper/index.ts | 4 ++ .../web-components/src/stepper/stepper.ts | 19 +++++++++ packages/web-components/src/switch/index.ts | 4 ++ .../web-components/src/text-area/index.ts | 4 ++ .../web-components/src/text-field/index.ts | 4 ++ 15 files changed, 125 insertions(+), 3 deletions(-) diff --git a/packages/web-components/src/button/icon-button/index.ts b/packages/web-components/src/button/icon-button/index.ts index 584139f1..e6e2cc50 100644 --- a/packages/web-components/src/button/icon-button/index.ts +++ b/packages/web-components/src/button/icon-button/index.ts @@ -18,6 +18,10 @@ export { Slots } from "./constants.ts"; * @prop {boolean} [loading=false] - Whether the button is in a loading state. * @prop {'sm' | 'md' | 'lg'} [size='md'] - The size of the button. * @prop {'primary' | 'ghost' | 'naked' | 'elevated' | 'destructive' | 'brand'} [variant='primary'] - The variant style of the button. + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus */ @customElement("tapsi-icon-button") export class TapsiIconButton extends IconButton { diff --git a/packages/web-components/src/button/standard/index.ts b/packages/web-components/src/button/standard/index.ts index 7f8cbf2a..884ce703 100644 --- a/packages/web-components/src/button/standard/index.ts +++ b/packages/web-components/src/button/standard/index.ts @@ -19,6 +19,10 @@ export { Slots } from "./constants.ts"; * @prop {boolean} [loading=false] - Whether the button is in a loading state. * @prop {'sm' | 'md' | 'lg'} [size='md'] - The size of the button. * @prop {'primary' | 'ghost' | 'naked' | 'elevated' | 'destructive' | 'brand'} [variant='primary'] - The variant style of the button. + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus */ @customElement("tapsi-button") export class TapsiButton extends Button { diff --git a/packages/web-components/src/checkbox/index.ts b/packages/web-components/src/checkbox/index.ts index 1e49051b..bd9793d5 100644 --- a/packages/web-components/src/checkbox/index.ts +++ b/packages/web-components/src/checkbox/index.ts @@ -21,6 +21,10 @@ import { Checkbox } from "./checkbox.ts"; * owning form can be submitted and will render an error state when * `reportValidity()` is invoked when value is empty.\ * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus */ @customElement("tapsi-checkbox") export class TapsiCheckbox extends Checkbox { diff --git a/packages/web-components/src/file-input/file-input.ts b/packages/web-components/src/file-input/file-input.ts index c113fc0e..aa498db7 100644 --- a/packages/web-components/src/file-input/file-input.ts +++ b/packages/web-components/src/file-input/file-input.ts @@ -17,6 +17,7 @@ import { logger, onReportValidity, redispatchEvent, + runAfterRepaint, toFaNumber, waitAMicrotask, withConstraintValidation, @@ -52,7 +53,24 @@ export class FileInput extends BaseClass { * - If `true`, a spinner will appear indicating the component is loading. * - If a number between 0 and 100, it shows the percentage of the loading state. */ - @property() + @property({ + converter: { + fromAttribute(value: string | null): boolean | number { + if (value === null) return false; + + const numericValue = Number(value); + + if (Number.isNaN(numericValue)) return true; + + return numericValue; + }, + toAttribute(value: boolean | number): string | null { + if (typeof value === "boolean") return value ? "true" : null; + + return `${value}`; + }, + }, + }) public loading: boolean | number = false; /** @@ -161,6 +179,14 @@ export class FileInput extends BaseClass { @property({ type: Boolean, reflect: true }) public required = false; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + @state() private _hasPlaceholderIconSlot = false; @@ -196,6 +222,16 @@ export class FileInput extends BaseClass { this._handleActivationClick = this._handleActivationClick.bind(this); } + protected override firstUpdated(changed: PropertyValues): void { + super.firstUpdated(changed); + + runAfterRepaint(() => { + if (!this.autofocus) return; + + this.focus(); + }); + } + protected override willUpdate(changed: PropertyValues) { super.willUpdate(changed); @@ -476,7 +512,7 @@ export class FileInput extends BaseClass { if (isFileImage(file.name)) { return html`preview`; diff --git a/packages/web-components/src/file-input/index.ts b/packages/web-components/src/file-input/index.ts index 4c9efab5..49cf5ade 100644 --- a/packages/web-components/src/file-input/index.ts +++ b/packages/web-components/src/file-input/index.ts @@ -76,6 +76,10 @@ export * from "./events.ts"; * value. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#readonly + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @method reset * @description - resets the input value diff --git a/packages/web-components/src/pin-input/index.ts b/packages/web-components/src/pin-input/index.ts index 2fd993b6..aee73042 100644 --- a/packages/web-components/src/pin-input/index.ts +++ b/packages/web-components/src/pin-input/index.ts @@ -36,6 +36,10 @@ export * from "./events.ts"; * should provide. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * @prop {string} [supporting-text=""] - * Conveys additional information below the text input, such as how it should * be used. diff --git a/packages/web-components/src/pinwheel/index.ts b/packages/web-components/src/pinwheel/index.ts index ddd8cbc4..3b266420 100644 --- a/packages/web-components/src/pinwheel/index.ts +++ b/packages/web-components/src/pinwheel/index.ts @@ -45,6 +45,10 @@ export class TapsiPinwheelItem extends PinwheelItem { * Identifies the element (or elements) that labels the pinwheel. * * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @fires {Event} change - Fires when the pinwheel selected state changes (bubbles). */ diff --git a/packages/web-components/src/radio/index.ts b/packages/web-components/src/radio/index.ts index 4652506a..d768c643 100644 --- a/packages/web-components/src/radio/index.ts +++ b/packages/web-components/src/radio/index.ts @@ -19,6 +19,10 @@ import { Radio } from "./radio.ts"; * owning form can be submitted and will render an error state when * `reportValidity()` is invoked when value is empty.\ * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus */ @customElement("tapsi-radio") export class TapsiRadio extends Radio { diff --git a/packages/web-components/src/rate-slider/index.ts b/packages/web-components/src/rate-slider/index.ts index 28fb4eac..e86f3f09 100644 --- a/packages/web-components/src/rate-slider/index.ts +++ b/packages/web-components/src/rate-slider/index.ts @@ -31,6 +31,10 @@ import { RateSlider } from "./rate-slider.ts"; * Defaults to "0". * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#min + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @method stepDown * @description - Decrements the value of the input. diff --git a/packages/web-components/src/rate-slider/rate-slider.ts b/packages/web-components/src/rate-slider/rate-slider.ts index fdeeff6a..b19b9cc3 100644 --- a/packages/web-components/src/rate-slider/rate-slider.ts +++ b/packages/web-components/src/rate-slider/rate-slider.ts @@ -1,4 +1,4 @@ -import { html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing, type PropertyValues } from "lit"; import { property, query, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { map } from "lit/directives/map.js"; @@ -14,6 +14,7 @@ import { isActivationClick, isSsr, logger, + runAfterRepaint, toFaNumber, waitAMicrotask, withElementInternals, @@ -96,6 +97,14 @@ export class RateSlider extends BaseClass { @property({ type: String }) public min = `${DEFAULT_MIN}`; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + @query("#root") private _root!: HTMLElement | null; @@ -128,6 +137,16 @@ export class RateSlider extends BaseClass { } } + protected override firstUpdated(changed: PropertyValues): void { + super.firstUpdated(changed); + + runAfterRepaint(() => { + if (!this.autofocus) return; + + this.focus(); + }); + } + public override connectedCallback() { super.connectedCallback(); // eslint-disable-next-line @typescript-eslint/no-misused-promises diff --git a/packages/web-components/src/stepper/index.ts b/packages/web-components/src/stepper/index.ts index 42f26405..b2ac871c 100644 --- a/packages/web-components/src/stepper/index.ts +++ b/packages/web-components/src/stepper/index.ts @@ -44,6 +44,10 @@ import { Stepper } from "./stepper.ts"; * Defaults to "1". * * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @fires {Event} change - Fires when value changes (bubbles). */ diff --git a/packages/web-components/src/stepper/stepper.ts b/packages/web-components/src/stepper/stepper.ts index 10713720..437eb75a 100644 --- a/packages/web-components/src/stepper/stepper.ts +++ b/packages/web-components/src/stepper/stepper.ts @@ -12,6 +12,7 @@ import { isActivationClick, isSsr, logger, + runAfterRepaint, toFaNumber, waitAMicrotask, withElementInternals, @@ -126,6 +127,14 @@ export class Stepper extends BaseClass { @property({ type: String }) public step = "1"; + /** + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus + */ + @property({ type: Boolean }) + public override autofocus = false; + constructor() { super(); @@ -163,6 +172,16 @@ export class Stepper extends BaseClass { this.removeEventListener("keydown", this._handleKeyDown); } + protected override firstUpdated(changed: PropertyValues): void { + super.firstUpdated(changed); + + runAfterRepaint(() => { + if (!this.autofocus) return; + + this.focus(); + }); + } + protected override updated(changed: PropertyValues) { super.updated(changed); diff --git a/packages/web-components/src/switch/index.ts b/packages/web-components/src/switch/index.ts index bb3e1d3e..917f937c 100644 --- a/packages/web-components/src/switch/index.ts +++ b/packages/web-components/src/switch/index.ts @@ -19,6 +19,10 @@ import { Switch } from "./switch.ts"; * owning form can be submitted and will render an error state when * `reportValidity()` is invoked when value is empty.\ * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus */ @customElement("tapsi-switch") export class TapsiSwitch extends Switch { diff --git a/packages/web-components/src/text-area/index.ts b/packages/web-components/src/text-area/index.ts index bb06167d..633aa3f4 100644 --- a/packages/web-components/src/text-area/index.ts +++ b/packages/web-components/src/text-area/index.ts @@ -95,6 +95,10 @@ export { BaseTextInputSlots as Slots }; * appropriate virtual keyboard. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @slot leading-icon - the leading icon slot of the text-area * @slot trailing - the trailing slot of the text-area diff --git a/packages/web-components/src/text-field/index.ts b/packages/web-components/src/text-field/index.ts index 5b11a5b1..18d9d16d 100644 --- a/packages/web-components/src/text-field/index.ts +++ b/packages/web-components/src/text-field/index.ts @@ -123,6 +123,10 @@ export { BaseTextInputSlots as Slots }; * appropriate virtual keyboard. * * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode + * @prop {boolean} [autofocus=false] - + * Indicates that the element should be focused on page load. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus * * @slot leading-icon - the leading icon slot of the text-area * @slot trailing - the trailing slot of the text-area From 2f97c1635d028539992b067c2421f78223193abd Mon Sep 17 00:00:00 2001 From: mimshins Date: Wed, 5 Mar 2025 17:49:44 +0330 Subject: [PATCH 3/3] fix loading converter --- packages/web-components/src/file-input/file-input.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web-components/src/file-input/file-input.ts b/packages/web-components/src/file-input/file-input.ts index aa498db7..83ba5a95 100644 --- a/packages/web-components/src/file-input/file-input.ts +++ b/packages/web-components/src/file-input/file-input.ts @@ -57,6 +57,7 @@ export class FileInput extends BaseClass { converter: { fromAttribute(value: string | null): boolean | number { if (value === null) return false; + if (value === "") return true; const numericValue = Number(value); @@ -645,6 +646,8 @@ export class FileInput extends BaseClass { } protected override render() { + console.log(this.loading); + const rootClasses = classMap({ root: true, disabled: this.disabled,