Skip to content

Commit

Permalink
Enhance error list functionality: improve scrolling and focus behavio…
Browse files Browse the repository at this point in the history
…r for error links

Refs: #7310
  • Loading branch information
deleonio committed Jan 29, 2025
1 parent 2c4b8eb commit 2b6505b
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 51 deletions.
52 changes: 27 additions & 25 deletions packages/components/src/components/form/shadow.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { JSX } from '@stencil/core';
import { Component, Element, h, h, Host, Method, Prop, State, Watch } from '@stencil/core';
import { validateErrorList, watchBoolean, watchString } from '../../schema';
import { Component, Element, h, Host, Prop, State, Watch, Method } from '@stencil/core';

import { translate } from '../../i18n';

import type { ErrorListPropType, FormAPI, FormStates, KoliBriFormCallbacks, Stringified } from '../../schema';
import { KolLinkWcTag } from '../../core/component-names';
import KolAlertFc from '../../functional-components/Alert';
import type { ErrorListPropType, FormAPI, FormStates, KoliBriFormCallbacks, Stringified } from '../../schema';
import { dispatchDomEvent, KolEvent } from '../../utils/events';

/**
Expand All @@ -21,7 +21,8 @@ import { dispatchDomEvent, KolEvent } from '../../utils/events';
})
export class KolForm implements FormAPI {
@Element() private readonly host?: HTMLKolTextareaElement;
errorListElement?: HTMLElement;
errorListBlock?: HTMLElement;
errorListFirstLink?: HTMLElement;

/* Hint: This method may not be used at all while events are handled in form/controller#propagateSubmitEventToForm */
private readonly onSubmit = (event: Event) => {
Expand All @@ -45,16 +46,11 @@ export class KolForm implements FormAPI {
}
};

private readonly handleLinkClick = (event: Event) => {
const href = (event.target as HTMLAnchorElement | undefined)?.href;
if (href) {
const hrefUrl = new URL(href);

const targetElement = document.querySelector<HTMLElement>(hrefUrl.hash);
if (targetElement && typeof targetElement.focus === 'function') {
targetElement.scrollIntoView({ behavior: 'smooth' });
targetElement.focus();
}
private readonly handleLinkClick = (selector: string) => {
const targetElement = document.querySelector<HTMLElement>(selector);
if (targetElement && typeof targetElement.focus === 'function') {
targetElement.scrollIntoView({ behavior: 'smooth' });
targetElement.focus();
}
};

Expand All @@ -63,12 +59,7 @@ export class KolForm implements FormAPI {
<KolAlertFc
class="kol-form__alert"
ref={(el) => {
setTimeout(() => {
if (el && typeof el.focus === 'function') {
el.scrollIntoView({ behavior: 'smooth' });
el.focus();
}
}, 250);
this.errorListBlock = el;
}}
type="error"
variant="card"
Expand All @@ -80,11 +71,14 @@ export class KolForm implements FormAPI {
<li key={index}>
<KolLinkWcTag
class="kol-form__link"
_href={error.selector}
_href=""
_label={error.message}
_on={{ onClick: this.handleLinkClick }}
_on={{ onClick: typeof error.selector === 'string' ? () => this.handleLinkClick(String(error.selector)) : error.selector }}
ref={(el) => {
if (index === 0) this.errorListElement = el;
if (index === 0) {
this.errorListFirstLink = el;
this.scrollToErrorList();
}
}}
/>
</li>
Expand Down Expand Up @@ -123,11 +117,19 @@ export class KolForm implements FormAPI {
);
}

private scrollToErrorList(): void {
this.errorListBlock?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
setTimeout(() => {
this.errorListFirstLink?.querySelector('a')?.focus();
}, 250);
}

@Method()
async focusErrorList(): Promise<void> {
if (this._errorList && this._errorList.length > 0) {
this.errorListElement?.focus();
}
this.scrollToErrorList();
return Promise.resolve();
}

Expand Down
11 changes: 9 additions & 2 deletions packages/components/src/schema/props/error-list.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Generic } from 'adopted-style-sheets';
import { EventCallback } from '../types';
import { watchValidator } from '../utils';

/* types */
export type ErrorListPropType = {
message: string;
selector: string;
selector: string | EventCallback<Event>;
};

export type PropErrorList = {
Expand All @@ -13,5 +14,11 @@ export type PropErrorList = {

/* validator */
export const validateErrorList = (component: Generic.Element.Component, value?: ErrorListPropType[]): void => {
watchValidator(component, 'errorList', (value): boolean => typeof value === 'object', new Set(['Object']), value);
watchValidator(
component,
'errorList',
(value): boolean => Array.isArray(value) && value.find((v) => !(typeof v === 'string' || typeof v === 'function')) === undefined,
new Set(['string', 'function']),
value,
);
};
62 changes: 38 additions & 24 deletions packages/samples/react/src/components/form/error-list.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { KolButton, KolForm, KolInputText } from '@public-ui/react';
import type { FC } from 'react';
import React from 'react';
import React, { useRef } from 'react';
import { SampleDescription } from '../SampleDescription';
import { KolForm, KolInputText } from '@public-ui/react';

export const FormErrorList: FC = () => (
<>
<SampleDescription>
<p>This sample shows a form with error messages.</p>
</SampleDescription>
export const FormErrorList: FC = () => {
const formRef = useRef<HTMLKolFormElement>();
return (
<>
<SampleDescription>
<p>This sample shows a form with error messages.</p>
</SampleDescription>

<KolForm
className="w-full"
_errorList={[
{
message: 'Error in Input 2',
selector: '#input2',
},
]}
>
<div className="grid gap-2">
<KolInputText id="input1" _label="Input 1" />
<KolInputText id="input2" _label="Input 2" _touched _msg={{ _description: 'Input error', _type: 'error' }} />
<KolInputText id="input3" _label="Input 3" />
</div>
</KolForm>
</>
);
<KolForm
className="w-full"
ref={formRef}
_errorList={[
{
message: 'Error in Input 2',
selector: '#input2',
},
]}
>
<div className="grid gap-2">
<KolInputText id="input1" _label="Input 1" />
<KolInputText id="input2" _label="Input 2" _touched _msg={{ _description: 'Input error', _type: 'error' }} />
<KolInputText id="input3" _label="Input 3" />
<div>
<KolButton
_label="ScrollTo"
_on={{
onClick: () => {
formRef.current?.focusErrorList();
},
}}
/>
</div>
</div>
</KolForm>
</>
);
};

0 comments on commit 2b6505b

Please sign in to comment.