Skip to content

Commit 6af5246

Browse files
Copilotmfranzkenmergetgithub-actions[bot]Copilot
authored
fix(DBCustomSelect): form reset functionality (#4853)
* Initial plan * Initial analysis of DBCustomSelect form reset issue Co-authored-by: mfranzke <787658+mfranzke@users.noreply.github.com> * Implement form reset handling for DBCustomSelect Co-authored-by: mfranzke <787658+mfranzke@users.noreply.github.com> * Successful test validation of DBCustomSelect form reset fix Co-authored-by: mfranzke <787658+mfranzke@users.noreply.github.com> * feat: add form reset for every form component * fix: linting issues * refactor: formatting * fix: linting issues * fix: typescript issue * chore: add changeset * fix: issue with react showcase * auto update snapshots (#5082) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: issue with showcase-home.spec.ts * auto update snapshots (#5145) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * auto update snapshots (#5146) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * auto update snapshots (#5173) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: issues with onChange handler for angular chore: add tests for switch * fix: linting issues * Update showcases/vue-showcase/src/components/form/Custom-Selects.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mfranzke <787658+mfranzke@users.noreply.github.com> Co-authored-by: Nicolas Merget <nicolas.merget@deutschebahn.com> Co-authored-by: Maximilian Franzke <maximilian.franzke@deutschebahn.com> Co-authored-by: Nicolas Merget <104347736+nmerget@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d1fd2c4 commit 6af5246

File tree

38 files changed

+776
-79
lines changed

38 files changed

+776
-79
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@db-ux/core-components": patch
3+
"@db-ux/ngx-core-components": patch
4+
"@db-ux/react-core-components": patch
5+
"@db-ux/v-core-components": patch
6+
"@db-ux/wc-core-components": patch
7+
---
8+
9+
fix(DBCustomSelect): automatically handle form reset events
10+
11+
An event listener is now added for every form component (input, custom-select, etc.) when a `form` property is passed.
12+
This listener detects form resets and updates the component's internal value/checked state accordingly.
13+
14+
> **Note**: This does not work for `ngModel` in Angular.
-2.84 KB
Loading

packages/components/scripts/post-build/components.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ export const getComponents = (): Component[] => [
178178
to: '{{value()}}</textarea>'
179179
}
180180
],
181+
vue: [
182+
{
183+
from: '</textarea>',
184+
to: '{{value}}</textarea>'
185+
}
186+
],
181187
react: [{ from: /HTMLAttributes/g, to: 'TextareaHTMLAttributes' }],
182188
stencil: [{ from: 'HTMLElement', to: 'HTMLTextAreaElement' }]
183189
}
@@ -214,9 +220,7 @@ export const getComponents = (): Component[] => [
214220
{
215221
name: 'select',
216222
overwrites: {
217-
angular: [
218-
{ from: '<HTMLElement>', to: '<HTMLSelectElement>' }
219-
],
223+
angular: [{ from: '<HTMLElement>', to: '<HTMLSelectElement>' }],
220224
react: [
221225
// React not allowing selected for options
222226
{ from: 'selected={option.selected}', to: '' },

packages/components/src/components/checkbox/checkbox.lite.tsx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
onMount,
3+
onUnMount,
34
onUpdate,
45
Show,
56
useDefaultProps,
@@ -26,6 +27,7 @@ import {
2627
uuid
2728
} from '../../utils';
2829
import {
30+
addCheckedResetEventListener,
2931
handleFrameworkEventAngular,
3032
handleFrameworkEventVue
3133
} from '../../utils/form-components';
@@ -55,6 +57,7 @@ export default function DBCheckbox(props: DBCheckboxProps) {
5557
_invalidMessage: undefined,
5658
_descByIds: undefined,
5759
_voiceOverFallback: '',
60+
abortController: undefined,
5861
hasValidState: () => {
5962
return !!(props.validMessage ?? props.validation === 'valid');
6063
},
@@ -87,10 +90,25 @@ export default function DBCheckbox(props: DBCheckboxProps) {
8790
state._descByIds = undefined;
8891
}
8992
},
90-
handleChange: (event: ChangeEvent<HTMLInputElement>) => {
91-
if (props.onChange) {
92-
props.onChange(event);
93-
}
93+
handleChange: (
94+
event: ChangeEvent<HTMLInputElement>,
95+
reset?: boolean
96+
) => {
97+
useTarget({
98+
angular: () => {
99+
if (props.onChange) {
100+
// We need to split the if statements for generation
101+
if (reset) {
102+
props.onChange(event);
103+
}
104+
}
105+
},
106+
default: () => {
107+
if (props.onChange) {
108+
props.onChange(event);
109+
}
110+
}
111+
});
94112

95113
useTarget({
96114
angular: () =>
@@ -177,6 +195,34 @@ export default function DBCheckbox(props: DBCheckboxProps) {
177195
state.initialized = false;
178196
}
179197
}, [state.initialized, _ref, props.checked]);
198+
199+
onUpdate(() => {
200+
if (_ref) {
201+
const defaultChecked = useTarget({
202+
react: (props as any).defaultChecked,
203+
default: undefined
204+
});
205+
206+
let controller = state.abortController;
207+
if (!controller) {
208+
controller = new AbortController();
209+
state.abortController = controller;
210+
}
211+
212+
addCheckedResetEventListener(
213+
_ref,
214+
{ checked: props.checked, defaultChecked },
215+
(event) => {
216+
state.handleChange(event, true);
217+
},
218+
controller.signal
219+
);
220+
}
221+
}, [_ref]);
222+
223+
onUnMount(() => {
224+
state.abortController?.abort();
225+
});
180226
// jscpd:ignore-end
181227

182228
return (

packages/components/src/components/checkbox/docs/HTML.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ To add a descriptive text with HTML formatting (e.g., bold or italic text) to a
3232
/>
3333
Label
3434
</label>
35-
<p class="db-infotext" id="checkbox-message">Example <strong>Text</strong></p>
35+
<p class="db-infotext" id="checkbox-message">
36+
Example <strong>Text</strong>
37+
</p>
3638
</body>
3739
```

packages/components/src/components/checkbox/docs/React.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const App = () => {
4747

4848
{/* The DBInfotext component holds the formatted message. */}
4949
<DBInfotext id="checkbox-message">
50-
Example <strong>Text</strong>
50+
Example <strong>Text</strong>
5151
</DBInfotext>
5252
</div>
5353
);

packages/components/src/components/checkbox/docs/Vue.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import { DBCheckbox, DBInfotext } from "@db-ux/v-core-components";
3333
Example Checkbox
3434
</DBCheckbox>
3535
36-
<DBInfotext id="checkbox-message"> Example <strong>Text</strong> </DBInfotext>
36+
<DBInfotext id="checkbox-message">
37+
Example <strong>Text</strong>
38+
</DBInfotext>
3739
</template>
3840
```

packages/components/src/components/custom-select/custom-select.lite.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import {
33
For,
44
onMount,
5+
onUnMount,
56
onUpdate,
67
Show,
78
useDefaultProps,
@@ -48,6 +49,7 @@ import { DocumentClickListener } from '../../utils/document-click-listener';
4849
import { DocumentScrollListener } from '../../utils/document-scroll-listener';
4950
import { handleFixedDropdown } from '../../utils/floating-components';
5051
import {
52+
addResetEventListener,
5153
handleFrameworkEventAngular,
5254
handleFrameworkEventVue
5355
} from '../../utils/form-components';
@@ -103,6 +105,7 @@ export default function DBCustomSelect(props: DBCustomSelectProps) {
103105
_infoTextId: undefined,
104106
_validity: 'no-validation',
105107
_userInteraction: false,
108+
abortController: undefined,
106109
// Workaround for Vue output: TS for Vue would think that it could be a function, and by this we clarify that it's a string
107110
_descByIds: undefined,
108111
_selectedLabels: '',
@@ -750,6 +753,31 @@ export default function DBCustomSelect(props: DBCustomSelectProps) {
750753
}
751754
}, [state._values, selectRef]);
752755

756+
onUpdate(() => {
757+
if (selectRef) {
758+
let controller = state.abortController;
759+
if (!controller) {
760+
controller = new AbortController();
761+
state.abortController = controller;
762+
}
763+
764+
const initialValues = props.values;
765+
addResetEventListener(
766+
selectRef,
767+
() => {
768+
const resetValue = initialValues
769+
? initialValues
770+
: selectRef.value
771+
? [selectRef.value]
772+
: [];
773+
state.handleOptionSelected(resetValue);
774+
state.handleValidation();
775+
},
776+
controller.signal
777+
);
778+
}
779+
}, [selectRef]);
780+
753781
onUpdate(() => {
754782
state._validity = props.validation;
755783
}, [props.validation]);
@@ -853,6 +881,10 @@ export default function DBCustomSelect(props: DBCustomSelectProps) {
853881
DEFAULT_INVALID_MESSAGE;
854882
}, [selectRef, props.invalidMessage]);
855883

884+
onUnMount(() => {
885+
state.abortController?.abort();
886+
});
887+
856888
function satisfyReact(event: any) {
857889
// This is a function to satisfy React
858890
event.stopPropagation();

packages/components/src/components/input/input.lite.tsx

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
For,
33
onMount,
4+
onUnMount,
45
onUpdate,
56
Show,
67
useDefaultProps,
@@ -41,6 +42,7 @@ import {
4142
uuid
4243
} from '../../utils';
4344
import {
45+
addValueResetEventListener,
4446
handleFrameworkEventAngular,
4547
handleFrameworkEventVue
4648
} from '../../utils/form-components';
@@ -71,6 +73,7 @@ export default function DBInput(props: DBInputProps) {
7173
_descByIds: undefined,
7274
_value: undefined,
7375
_voiceOverFallback: '',
76+
abortController: undefined,
7477
hasValidState: () => {
7578
return !!(props.validMessage ?? props.validation === 'valid');
7679
},
@@ -106,8 +109,15 @@ export default function DBInput(props: DBInputProps) {
106109
state._descByIds = undefined;
107110
}
108111
},
109-
handleInput: (event: InputEvent<HTMLInputElement>) => {
112+
handleInput: (event: InputEvent<HTMLInputElement>, reset?: boolean) => {
110113
useTarget({
114+
angular: () => {
115+
if (props.onInput) {
116+
if (reset) {
117+
props.onInput(event);
118+
}
119+
}
120+
},
111121
vue: () => {
112122
if (props.input) {
113123
props.input(event);
@@ -129,10 +139,25 @@ export default function DBInput(props: DBInputProps) {
129139
});
130140
state.handleValidation();
131141
},
132-
handleChange: (event: ChangeEvent<HTMLInputElement>) => {
133-
if (props.onChange) {
134-
props.onChange(event);
135-
}
142+
handleChange: (
143+
event: ChangeEvent<HTMLInputElement>,
144+
reset?: boolean
145+
) => {
146+
useTarget({
147+
angular: () => {
148+
if (props.onChange) {
149+
// We need to split the if statements for generation
150+
if (reset) {
151+
props.onChange(event);
152+
}
153+
}
154+
},
155+
default: () => {
156+
if (props.onChange) {
157+
props.onChange(event);
158+
}
159+
}
160+
});
136161

137162
useTarget({
138163
angular: () => handleFrameworkEventAngular(state, event),
@@ -202,6 +227,35 @@ export default function DBInput(props: DBInputProps) {
202227
state._value = props.value;
203228
}, [props.value]);
204229

230+
onUpdate(() => {
231+
if (_ref) {
232+
const defaultValue = useTarget({
233+
react: (props as any).defaultValue,
234+
default: undefined
235+
});
236+
237+
let controller = state.abortController;
238+
if (!controller) {
239+
controller = new AbortController();
240+
state.abortController = controller;
241+
}
242+
243+
addValueResetEventListener(
244+
_ref,
245+
{ value: props.value, defaultValue },
246+
(event) => {
247+
state.handleChange(event, true);
248+
state.handleInput(event, true);
249+
},
250+
controller.signal
251+
);
252+
}
253+
}, [_ref]);
254+
255+
onUnMount(() => {
256+
state.abortController?.abort();
257+
});
258+
205259
return (
206260
<div
207261
class={cls('db-input', props.className)}

packages/components/src/components/input/input.spec.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ const testAction = () => {
5050
expect(test).toEqual('test');
5151
});
5252

53-
test('should have enterkeyhint attribute when provided', async ({ mount }) => {
53+
test('should have enterkeyhint attribute when provided', async ({
54+
mount
55+
}) => {
5456
const component = await mount(
5557
<DBInput label="Label" enterkeyhint="done" />
5658
);
@@ -66,12 +68,10 @@ const testAction = () => {
6668
await expect(input).toHaveAttribute('inputmode', 'numeric');
6769
});
6870

69-
70-
71-
test('should not have enterkeyhint or inputmode when not provided', async ({ mount }) => {
72-
const component = await mount(
73-
<DBInput label="Label" type="text" />
74-
);
71+
test('should not have enterkeyhint or inputmode when not provided', async ({
72+
mount
73+
}) => {
74+
const component = await mount(<DBInput label="Label" type="text" />);
7575
const input = component.getByRole('textbox');
7676
await expect(input).not.toHaveAttribute('enterkeyhint');
7777
await expect(input).not.toHaveAttribute('inputmode');

0 commit comments

Comments
 (0)