Skip to content

Commit 38f708d

Browse files
committed
fix(core): register form element
- form element has now the same comportment as the native form elements fixes: #64 #62
1 parent a287099 commit 38f708d

File tree

27 files changed

+867
-89
lines changed

27 files changed

+867
-89
lines changed

packages/core/src/components-config.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,12 @@ export namespace Configuration {
572572
};
573573
'pop-radio-group'?: {
574574
/**
575-
* If `true`, apply the required property to all `pop-radio`.
575+
* If `true`, apply the required property to every `pop-radio`.
576576
* @default false
577577
*/
578578
required?: boolean;
579579
/**
580-
* If `true`, apply the disabled property to all `pop-radio`.
580+
* If `true`, apply the disabled property to every `pop-radio`.
581581
* @default false
582582
*/
583583
disabled?: boolean;

packages/core/src/components.d.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ export namespace Components {
645645
"size"?: Size;
646646
/**
647647
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
648+
* @default null
648649
*/
649650
"value"?: File | File[] | null;
650651
}
@@ -907,7 +908,7 @@ export namespace Components {
907908
*/
908909
"compare"?: RadioGroupCompareFn | string | null;
909910
/**
910-
* If `true`, apply the disabled property to all `pop-radio`.
911+
* If `true`, apply the disabled property to every `pop-radio`.
911912
* @config
912913
* @default false
913914
*/
@@ -917,7 +918,7 @@ export namespace Components {
917918
*/
918919
"name": string;
919920
/**
920-
* If `true`, apply the required property to all `pop-radio`.
921+
* If `true`, apply the required property to every `pop-radio`.
921922
* @config
922923
* @default false
923924
*/
@@ -1277,7 +1278,7 @@ export namespace Components {
12771278
* The value of the textarea.
12781279
* @default ""
12791280
*/
1280-
"value"?: string | null;
1281+
"value"?: string;
12811282
/**
12821283
* Indicates how the control wraps text. If wrap attribute is in the `hard` state, the `cols` property must be specified.
12831284
* @config
@@ -2645,6 +2646,7 @@ declare namespace LocalJSX {
26452646
"size"?: Size;
26462647
/**
26472648
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
2649+
* @default null
26482650
*/
26492651
"value"?: File | File[] | null;
26502652
}
@@ -2922,7 +2924,7 @@ declare namespace LocalJSX {
29222924
*/
29232925
"compare"?: RadioGroupCompareFn | string | null;
29242926
/**
2925-
* If `true`, apply the disabled property to all `pop-radio`.
2927+
* If `true`, apply the disabled property to every `pop-radio`.
29262928
* @config
29272929
* @default false
29282930
*/
@@ -2940,7 +2942,7 @@ declare namespace LocalJSX {
29402942
*/
29412943
"onPopValueChange"?: (event: PopRadioGroupCustomEvent<RadioGroupChangeEventDetail>) => void;
29422944
/**
2943-
* If `true`, apply the required property to all `pop-radio`.
2945+
* If `true`, apply the required property to every `pop-radio`.
29442946
* @config
29452947
* @default false
29462948
*/
@@ -3334,7 +3336,7 @@ declare namespace LocalJSX {
33343336
* The value of the textarea.
33353337
* @default ""
33363338
*/
3337-
"value"?: string | null;
3339+
"value"?: string;
33383340
/**
33393341
* Indicates how the control wraps text. If wrap attribute is in the `hard` state, the `cols` property must be specified.
33403342
* @config

packages/core/src/components/checkbox/checkbox.scss

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@
129129

130130
@mixin generate-color($colors...) {
131131
@each $color in $colors {
132-
$base: use_color("#{$color}.base");
133-
$border: use_color("#{$color}.base", 0.2);
134-
$text: use_color("#{$color}.content");
132+
$base: theme.use_color("#{$color}.base");
133+
$border: theme.use_color("#{$color}.base", 0.2);
134+
$text: theme.use_color("#{$color}.content");
135135

136136
:host([color="#{$color}"]) {
137137
--background: #{$base};
@@ -181,7 +181,7 @@
181181
var(--background) 57%
182182
);
183183
background-repeat: no-repeat;
184-
background-color: var(--background, use_color("base.content"));
184+
background-color: var(--background, theme.use_color("base.content"));
185185
animation: checkmark var(--animation-duration, 0.2s) ease-out;
186186
}
187187

packages/core/src/components/checkbox/checkbox.tsx

+23-5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ let checkboxIds = 0;
3838
export class Checkbox implements ComponentInterface {
3939
private inputId = `pop-cb-${checkboxIds++}`;
4040
private inheritedAttributes: Attributes;
41+
42+
private initialState: boolean | 'indeterminate';
4143
private nativeInput!: HTMLInputElement;
4244

4345
@Element() host!: HTMLElement;
@@ -83,13 +85,13 @@ export class Checkbox implements ComponentInterface {
8385
@Prop({ reflect: true, mutable: true }) checked?: boolean;
8486
@Watch('checked')
8587
onCheckedChange(newChecked: boolean): void {
86-
this.indeterminate = false;
88+
this.indeterminate = undefined;
8789

8890
this.popChange.emit({
8991
checked: newChecked,
90-
value: this.value || '',
92+
value: newChecked ? this.value : null,
9193
});
92-
this.internals.setFormValue(newChecked ? this.value : '', newChecked.toString());
94+
this.internals.setFormValue(newChecked ? this.value : null, newChecked.toString());
9395
this.internals.ariaChecked = newChecked.toString();
9496
}
9597

@@ -153,13 +155,27 @@ export class Checkbox implements ComponentInterface {
153155
@Event() popBlur: EventEmitter<void>;
154156

155157
formResetCallback(): void {
156-
this.checked = false;
158+
if (this.initialState === 'indeterminate') {
159+
this.indeterminate = true;
160+
return;
161+
}
162+
this.checked = this.initialState;
157163
}
158164

159165
formStateRestoreCallback(state: string): void {
160166
this.checked = state === 'true';
161167
}
162168

169+
connectedCallback(): void {
170+
if (!this.checked) {
171+
return;
172+
}
173+
174+
const data = new FormData();
175+
data.set(this.name, this.value);
176+
this.internals.setFormValue(data, data);
177+
}
178+
163179
componentWillLoad(): void {
164180
this.inheritedAttributes = inheritAriaAttributes(this.host);
165181

@@ -172,6 +188,8 @@ export class Checkbox implements ComponentInterface {
172188
size: config.get('defaultSize', 'md'),
173189
placement: 'start',
174190
});
191+
192+
this.initialState = this.indeterminate ? 'indeterminate' : this.checked;
175193
}
176194

177195
/**
@@ -238,7 +256,7 @@ export class Checkbox implements ComponentInterface {
238256
checked={checked}
239257
disabled={disabled}
240258
id={inputId}
241-
indeterminate={this.indeterminate}
259+
indeterminate={indeterminate}
242260
name={name}
243261
onBlur={this.onBlur}
244262
onChange={this.onChecked}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Checkbox | Poppy-ui</title>
8+
<link rel="stylesheet" href="/dist/poppy/poppy.css">
9+
<script type="module" src="/dist/poppy/poppy.esm.js"></script>
10+
<script nomodule src="/dist/poppy/poppy.js"></script>
11+
<style>
12+
main {
13+
width: 100vw;
14+
height: 100dvh;
15+
display: flex;
16+
flex-direction: column;
17+
gap: 1rem;
18+
padding: 1rem;
19+
20+
background-color: var(--base-200);
21+
}
22+
23+
section {
24+
display: flex;
25+
flex-direction: column;
26+
justify-content: center;
27+
gap: .35rem;
28+
}
29+
30+
div, form {
31+
display: flex;
32+
flex-direction: column;
33+
gap: .5rem;
34+
}
35+
</style>
36+
</head>
37+
38+
<body>
39+
<main>
40+
<section>
41+
<h2>Checkbox - form</h2>
42+
<div>
43+
<form>
44+
<pop-checkbox name="custom" error-text="test error">input label</pop-checkbox>
45+
<pop-checkbox name="custom-value" checked error-text="test error">input label</pop-checkbox>
46+
<pop-checkbox name="custom-value" indeterminate error-text="test error">input label</pop-checkbox>
47+
<input type="checkbox" name="native">
48+
<pop-button type="submit" color="primary">submit</pop-button>
49+
<pop-button type="reset" color="ghost">reset</pop-button>
50+
</form>
51+
</div>
52+
</section>
53+
</main>
54+
55+
<script>
56+
document.addEventListener('DOMContentLoaded', () => {
57+
console.log('load', getData());
58+
})
59+
document.querySelector('form').addEventListener('submit', ev => {
60+
ev.preventDefault();
61+
console.log('submit', getData());
62+
});
63+
64+
function getData() {
65+
const data = new FormData(document.querySelector('form'))
66+
const obj = {};
67+
68+
for (const key of data.keys()) {
69+
const values = data.getAll(key);
70+
obj[key] = values.length > 1 ? values : values[0];
71+
}
72+
return obj;
73+
}
74+
</script>
75+
</body>
76+
77+
</html>

packages/core/src/components/input-file/input-file.tsx

+27-24
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Watch,
1212
h,
1313
} from '@stencil/core';
14-
import type { Size } from 'src/interface';
14+
import type { FormAssociatedInterface, Size } from 'src/interface';
1515
import { componentConfig, config } from '#config';
1616
import { type Attributes, hostContext, inheritAriaAttributes, inheritAttributes } from '#utils/helpers';
1717
import { Show } from '../Show';
@@ -33,12 +33,11 @@ let inputIds = 0;
3333
shadow: true,
3434
formAssociated: true,
3535
})
36-
export class InputFile implements ComponentInterface {
36+
export class InputFile implements ComponentInterface, FormAssociatedInterface {
3737
private inputId = `pop-input-file-${inputIds++}`;
3838
private inheritedAttributes: Attributes;
3939

4040
private nativeInput!: HTMLInputElement;
41-
private debounceTimer: NodeJS.Timeout;
4241

4342
@Element() host!: HTMLElement;
4443

@@ -47,21 +46,25 @@ export class InputFile implements ComponentInterface {
4746
/**
4847
* The name of the control, which is submitted with the form data.
4948
*/
50-
@Prop() name: string = this.inputId;
49+
@Prop({ reflect: true }) name: string = this.inputId;
5150

5251
/**
5352
* The value of the toggle does not mean if it's checked or not, use the `checked`
5453
* property for that.
5554
*
5655
* The value of a toggle is analogous to the value of a `<input type="checkbox">`,
5756
* it's only used when the toggle participates in a native `<form>`.
57+
*
58+
* @default null
5859
*/
59-
@Prop({ mutable: true }) value?: File | File[] | null;
60+
@Prop({ mutable: true }) value?: File | File[] | null = new File([], '');
6061
@Watch('value')
61-
onValueChange(file: File): void {
62+
onValueChange(value: File | File[]): void {
6263
const data = new FormData();
63-
data.set(this.name, file);
64-
64+
const files = Array.isArray(value) ? value : [value];
65+
for (const file of files) {
66+
data.set(this.name, file);
67+
}
6568
this.internals.setFormValue(data, data);
6669
}
6770

@@ -72,7 +75,7 @@ export class InputFile implements ComponentInterface {
7275
* @config
7376
* @default false
7477
*/
75-
@Prop({ mutable: true }) multiple?: boolean;
78+
@Prop({ reflect: true, mutable: true }) multiple?: boolean;
7679

7780
/**
7881
* If `true`, the user must fill in a value before submitting a form.
@@ -158,19 +161,28 @@ export class InputFile implements ComponentInterface {
158161
@Event() popBlur: EventEmitter<void>;
159162

160163
formResetCallback(): void {
161-
this.value = null;
162-
this.nativeInput.value = null;
164+
this.value = new File([], '');
165+
this.nativeInput.value = '';
163166
}
164167

165168
formStateRestoreCallback(state: File): void {
166169
this.value = state;
167170
}
168171

172+
connectedCallback(): void {
173+
if (this.value === null) {
174+
return;
175+
}
176+
const files = Array.isArray(this.value) ? this.value : [this.value];
177+
this.onValueChange(files);
178+
}
179+
169180
componentWillLoad(): void {
170181
this.inheritedAttributes = {
171182
...inheritAriaAttributes(this.host),
172183
...inheritAttributes(this.host, ['tabindex', 'title', 'data-form-type']),
173184
};
185+
174186
componentConfig.apply(this, 'pop-input-file', {
175187
multiple: false,
176188
required: false,
@@ -182,18 +194,6 @@ export class InputFile implements ComponentInterface {
182194
});
183195
}
184196

185-
// TODO: Tester si ça fonctionne
186-
componentDidLoad(): void {
187-
const { value } = this;
188-
const files = Array.isArray(value) ? value : [value];
189-
190-
files.forEach((file, idx) => (this.nativeInput.files[idx] = file));
191-
}
192-
193-
disconnectedCallback(): void {
194-
clearTimeout(this.debounceTimer);
195-
}
196-
197197
/**
198198
* Sets focus on the native `input` in `pop-input-file`. Use this method instead of the global
199199
* `input.focus()`.
@@ -208,8 +208,11 @@ export class InputFile implements ComponentInterface {
208208
}
209209

210210
private onChange = (): void => {
211+
const files = this.getValue();
212+
this.value = files;
213+
211214
this.popChange.emit({
212-
value: this.getValue(),
215+
value: files,
213216
});
214217
};
215218

0 commit comments

Comments
 (0)