Skip to content

Commit

Permalink
fix(core): register form element
Browse files Browse the repository at this point in the history
- form element has now the same comportment as the native form elements

fixes: #64
  • Loading branch information
Sukaato committed Dec 7, 2024
1 parent a287099 commit 5f092e3
Show file tree
Hide file tree
Showing 27 changed files with 867 additions and 89 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/components-config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,12 +572,12 @@ export namespace Configuration {
};
'pop-radio-group'?: {
/**
* If `true`, apply the required property to all `pop-radio`.
* If `true`, apply the required property to every `pop-radio`.
* @default false
*/
required?: boolean;
/**
* If `true`, apply the disabled property to all `pop-radio`.
* If `true`, apply the disabled property to every `pop-radio`.
* @default false
*/
disabled?: boolean;
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ export namespace Components {
"size"?: Size;
/**
* 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>`.
* @default null
*/
"value"?: File | File[] | null;
}
Expand Down Expand Up @@ -907,7 +908,7 @@ export namespace Components {
*/
"compare"?: RadioGroupCompareFn | string | null;
/**
* If `true`, apply the disabled property to all `pop-radio`.
* If `true`, apply the disabled property to every `pop-radio`.
* @config
* @default false
*/
Expand All @@ -917,7 +918,7 @@ export namespace Components {
*/
"name": string;
/**
* If `true`, apply the required property to all `pop-radio`.
* If `true`, apply the required property to every `pop-radio`.
* @config
* @default false
*/
Expand Down Expand Up @@ -1277,7 +1278,7 @@ export namespace Components {
* The value of the textarea.
* @default ""
*/
"value"?: string | null;
"value"?: string;
/**
* Indicates how the control wraps text. If wrap attribute is in the `hard` state, the `cols` property must be specified.
* @config
Expand Down Expand Up @@ -2645,6 +2646,7 @@ declare namespace LocalJSX {
"size"?: Size;
/**
* 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>`.
* @default null
*/
"value"?: File | File[] | null;
}
Expand Down Expand Up @@ -2922,7 +2924,7 @@ declare namespace LocalJSX {
*/
"compare"?: RadioGroupCompareFn | string | null;
/**
* If `true`, apply the disabled property to all `pop-radio`.
* If `true`, apply the disabled property to every `pop-radio`.
* @config
* @default false
*/
Expand All @@ -2940,7 +2942,7 @@ declare namespace LocalJSX {
*/
"onPopValueChange"?: (event: PopRadioGroupCustomEvent<RadioGroupChangeEventDetail>) => void;
/**
* If `true`, apply the required property to all `pop-radio`.
* If `true`, apply the required property to every `pop-radio`.
* @config
* @default false
*/
Expand Down Expand Up @@ -3334,7 +3336,7 @@ declare namespace LocalJSX {
* The value of the textarea.
* @default ""
*/
"value"?: string | null;
"value"?: string;
/**
* Indicates how the control wraps text. If wrap attribute is in the `hard` state, the `cols` property must be specified.
* @config
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@

@mixin generate-color($colors...) {
@each $color in $colors {
$base: use_color("#{$color}.base");
$border: use_color("#{$color}.base", 0.2);
$text: use_color("#{$color}.content");
$base: theme.use_color("#{$color}.base");
$border: theme.use_color("#{$color}.base", 0.2);
$text: theme.use_color("#{$color}.content");

:host([color="#{$color}"]) {
--background: #{$base};
Expand Down Expand Up @@ -181,7 +181,7 @@
var(--background) 57%
);
background-repeat: no-repeat;
background-color: var(--background, use_color("base.content"));
background-color: var(--background, theme.use_color("base.content"));
animation: checkmark var(--animation-duration, 0.2s) ease-out;
}

Expand Down
28 changes: 23 additions & 5 deletions packages/core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ let checkboxIds = 0;
export class Checkbox implements ComponentInterface {
private inputId = `pop-cb-${checkboxIds++}`;
private inheritedAttributes: Attributes;

private initialState: boolean | 'indeterminate';
private nativeInput!: HTMLInputElement;

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

this.popChange.emit({
checked: newChecked,
value: this.value || '',
value: newChecked ? this.value : null,
});
this.internals.setFormValue(newChecked ? this.value : '', newChecked.toString());
this.internals.setFormValue(newChecked ? this.value : null, newChecked.toString());
this.internals.ariaChecked = newChecked.toString();
}

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

formResetCallback(): void {
this.checked = false;
if (this.initialState === 'indeterminate') {
this.indeterminate = true;
return;
}
this.checked = this.initialState;
}

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

connectedCallback(): void {
if (!this.checked) {
return;
}

const data = new FormData();
data.set(this.name, this.value);
this.internals.setFormValue(data, data);
}

componentWillLoad(): void {
this.inheritedAttributes = inheritAriaAttributes(this.host);

Expand All @@ -172,6 +188,8 @@ export class Checkbox implements ComponentInterface {
size: config.get('defaultSize', 'md'),
placement: 'start',
});

this.initialState = this.indeterminate ? 'indeterminate' : this.checked;
}

/**
Expand Down Expand Up @@ -238,7 +256,7 @@ export class Checkbox implements ComponentInterface {
checked={checked}
disabled={disabled}
id={inputId}
indeterminate={this.indeterminate}
indeterminate={indeterminate}
name={name}
onBlur={this.onBlur}
onChange={this.onChecked}
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/components/checkbox/tests/form/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkbox | Poppy-ui</title>
<link rel="stylesheet" href="/dist/poppy/poppy.css">
<script type="module" src="/dist/poppy/poppy.esm.js"></script>
<script nomodule src="/dist/poppy/poppy.js"></script>
<style>
main {
width: 100vw;
height: 100dvh;
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;

background-color: var(--base-200);
}

section {
display: flex;
flex-direction: column;
justify-content: center;
gap: .35rem;
}

div, form {
display: flex;
flex-direction: column;
gap: .5rem;
}
</style>
</head>

<body>
<main>
<section>
<h2>Checkbox - form</h2>
<div>
<form>
<pop-checkbox name="custom" error-text="test error">input label</pop-checkbox>
<pop-checkbox name="custom-value" checked error-text="test error">input label</pop-checkbox>
<pop-checkbox name="custom-value" indeterminate error-text="test error">input label</pop-checkbox>
<input type="checkbox" name="native">
<pop-button type="submit" color="primary">submit</pop-button>
<pop-button type="reset" color="ghost">reset</pop-button>
</form>
</div>
</section>
</main>

<script>
document.addEventListener('DOMContentLoaded', () => {
console.log('load', getData());
})
document.querySelector('form').addEventListener('submit', ev => {
ev.preventDefault();
console.log('submit', getData());
});

function getData() {
const data = new FormData(document.querySelector('form'))
const obj = {};

for (const key of data.keys()) {
const values = data.getAll(key);
obj[key] = values.length > 1 ? values : values[0];
}
return obj;
}
</script>
</body>

</html>
51 changes: 27 additions & 24 deletions packages/core/src/components/input-file/input-file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Watch,
h,
} from '@stencil/core';
import type { Size } from 'src/interface';
import type { FormAssociatedInterface, Size } from 'src/interface';
import { componentConfig, config } from '#config';
import { type Attributes, hostContext, inheritAriaAttributes, inheritAttributes } from '#utils/helpers';
import { Show } from '../Show';
Expand All @@ -33,12 +33,11 @@ let inputIds = 0;
shadow: true,
formAssociated: true,
})
export class InputFile implements ComponentInterface {
export class InputFile implements ComponentInterface, FormAssociatedInterface {
private inputId = `pop-input-file-${inputIds++}`;
private inheritedAttributes: Attributes;

private nativeInput!: HTMLInputElement;
private debounceTimer: NodeJS.Timeout;

@Element() host!: HTMLElement;

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

/**
* 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>`.
*
* @default null
*/
@Prop({ mutable: true }) value?: File | File[] | null;
@Prop({ mutable: true }) value?: File | File[] | null = new File([], '');
@Watch('value')
onValueChange(file: File): void {
onValueChange(value: File | File[]): void {
const data = new FormData();
data.set(this.name, file);

const files = Array.isArray(value) ? value : [value];
for (const file of files) {
data.set(this.name, file);
}
this.internals.setFormValue(data, data);
}

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

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

formResetCallback(): void {
this.value = null;
this.nativeInput.value = null;
this.value = new File([], '');
this.nativeInput.value = '';
}

formStateRestoreCallback(state: File): void {
this.value = state;
}

connectedCallback(): void {
if (this.value === null) {
return;
}
const files = Array.isArray(this.value) ? this.value : [this.value];
this.onValueChange(files);
}

componentWillLoad(): void {
this.inheritedAttributes = {
...inheritAriaAttributes(this.host),
...inheritAttributes(this.host, ['tabindex', 'title', 'data-form-type']),
};

componentConfig.apply(this, 'pop-input-file', {
multiple: false,
required: false,
Expand All @@ -182,18 +194,6 @@ export class InputFile implements ComponentInterface {
});
}

// TODO: Tester si ça fonctionne
componentDidLoad(): void {
const { value } = this;
const files = Array.isArray(value) ? value : [value];

files.forEach((file, idx) => (this.nativeInput.files[idx] = file));
}

disconnectedCallback(): void {
clearTimeout(this.debounceTimer);
}

/**
* Sets focus on the native `input` in `pop-input-file`. Use this method instead of the global
* `input.focus()`.
Expand All @@ -208,8 +208,11 @@ export class InputFile implements ComponentInterface {
}

private onChange = (): void => {
const files = this.getValue();
this.value = files;

this.popChange.emit({
value: this.getValue(),
value: files,
});
};

Expand Down
Loading

0 comments on commit 5f092e3

Please sign in to comment.