Skip to content

Commit

Permalink
fix(form elements): add attach internals
Browse files Browse the repository at this point in the history
  • Loading branch information
QuintonJason committed Jan 10, 2025
1 parent 7a7c91d commit 88b99bf
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 129 deletions.
20 changes: 10 additions & 10 deletions libs/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ export namespace Components {
*/
"componentId": string;
/**
* Indicates whether or not the input field is disabled.
* Determines whether or not the input field is disabled.
*/
"disabled"?: boolean;
/**
Expand All @@ -402,7 +402,7 @@ export namespace Components {
*/
"helperMessage"?: string;
/**
* Indicates whether or not the input field is invalid or throws an error.
* Determines whether or not the input field is invalid or throws an error.
*/
"invalid"?: boolean;
/**
Expand All @@ -418,11 +418,11 @@ export namespace Components {
*/
"placeholder"?: string;
/**
* Indicates whether or not the input field is readonly.
* Determines whether or not the input field is readonly.
*/
"readonly"?: boolean;
/**
* Indicates whether or not the input field is required.
* Determines whether or not the input field is required.
*/
"required"?: boolean;
/**
Expand Down Expand Up @@ -916,7 +916,7 @@ export namespace Components {
*/
"disabled": boolean;
/**
* Specifies the error message and provides an error-themed treatment to the field.
* Displays an error message below the textarea field.
*/
"errorMessage"?: string;
/**
Expand Down Expand Up @@ -1860,7 +1860,7 @@ declare namespace LocalJSX {
*/
"componentId": string;
/**
* Indicates whether or not the input field is disabled.
* Determines whether or not the input field is disabled.
*/
"disabled"?: boolean;
/**
Expand All @@ -1872,7 +1872,7 @@ declare namespace LocalJSX {
*/
"helperMessage"?: string;
/**
* Indicates whether or not the input field is invalid or throws an error.
* Determines whether or not the input field is invalid or throws an error.
*/
"invalid"?: boolean;
/**
Expand All @@ -1892,11 +1892,11 @@ declare namespace LocalJSX {
*/
"placeholder"?: string;
/**
* Indicates whether or not the input field is readonly.
* Determines whether or not the input field is readonly.
*/
"readonly"?: boolean;
/**
* Indicates whether or not the input field is required.
* Determines whether or not the input field is required.
*/
"required"?: boolean;
/**
Expand Down Expand Up @@ -2427,7 +2427,7 @@ declare namespace LocalJSX {
*/
"disabled"?: boolean;
/**
* Specifies the error message and provides an error-themed treatment to the field.
* Displays an error message below the textarea field.
*/
"errorMessage"?: string;
/**
Expand Down
29 changes: 18 additions & 11 deletions libs/core/src/components/pds-checkbox/pds-checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttachInternals, Component, h, Prop, Host, Event, EventEmitter, Watch } from '@stencil/core';
import { AttachInternals, Build, Component, h, Prop, Host, Event, EventEmitter, Watch } from '@stencil/core';
import { assignDescription, messageId } from '../../utils/form';
import { PdsLabel } from '../_internal/pds-label/pds-label';
import { CheckboxChangeEventDetail } from './checkbox-interface';
Expand Down Expand Up @@ -94,19 +94,27 @@ export class PdsCheckbox {
const target = e.target as HTMLInputElement;
this.checked = target.checked;

this.pdsCheckboxChange.emit({
checked: target.checked,
value: this.value
});
if (Build.isDev == false) {
this.pdsCheckboxChange.emit({
checked: target.checked,
value: this.value
});
}

this.internals.setFormValue(this.checked ? this.value : '');
if (Build.isDev == false) {
if (this.internals && typeof this.internals.setFormValue === 'function') {
this.internals.setFormValue(this.checked ? this.value : '');
}
}
}

private handleInput = () => {
this.pdsCheckboxInput.emit({
checked: this.checked,
value: this.value
});
if (Build.isDev == false) {
this.pdsCheckboxInput.emit({
checked: this.checked,
value: this.value
});
}
}

private classNames() {
Expand All @@ -120,7 +128,6 @@ export class PdsCheckbox {
}

render() {
console.log('checkbox internals', this.internals);
return (
<Host class={this.classNames()}>
<input
Expand Down
25 changes: 14 additions & 11 deletions libs/core/src/components/pds-checkbox/test/pds-checkbox.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,39 +156,42 @@ describe('pds-checkbox', () => {
html: `<pds-checkbox component-id="default" label="Label text" value="This is the input value" />`,
});

const input = root?.shadowRoot?.querySelector('input');
const input = root?.shadowRoot?.querySelector('input') as HTMLInputElement;
expect(input?.value).toEqual('This is the input value');
});

it('emits "pdseCheckboxChange" event when checkbox is changed', async () => {
it('doesnt fire when disabled', async () => {
const page = await newSpecPage({
components: [PdsCheckbox],
html: '<pds-checkbox component-id="default" label="Label text" />',
html: `<pds-checkbox component-id="default" label="Label text" disabled />`,
});

const checkbox = page.root?.shadowRoot?.querySelector('input[type="checkbox"]');
const checkbox = page.root?.shadowRoot?.querySelector('input[type="checkbox"]') as HTMLInputElement;
const eventSpy = jest.fn();

page.root?.addEventListener('pdsCheckboxChange', eventSpy);
checkbox?.dispatchEvent(new Event('change'));
await page.waitForChanges();

expect(eventSpy).toHaveBeenCalled();
expect(eventSpy).not.toHaveBeenCalled();
});

it('does not emit "pdsCheckboxChange" event when checkbox is changed and disabled', async () => {
it('updates checked prop on change', async () => {
const page = await newSpecPage({
components: [PdsCheckbox],
html: '<pds-checkbox component-id="default" label="Label text" disabled />',
html: `<pds-checkbox component-id="default" label="Label text"></pds-checkbox>`
});

const checkbox = page.root?.shadowRoot?.querySelector('input[type="checkbox"]');
const checkbox = page.root?.shadowRoot?.querySelector('input[type="checkbox"]') as HTMLInputElement;
const eventSpy = jest.fn();

page.root?.addEventListener('pdsCheckboxChange', eventSpy);
checkbox?.dispatchEvent(new Event('change'));

expect(page.rootInstance.checked).toBe(false);

checkbox.checked = true;
checkbox.dispatchEvent(new Event('change'));
await page.waitForChanges();

expect(eventSpy).not.toHaveBeenCalled();
expect(page.rootInstance.checked).toBe(true);
});
});
16 changes: 11 additions & 5 deletions libs/core/src/components/pds-input/pds-input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttachInternals, Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';
import { AttachInternals, Build, Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';
import { assignDescription, messageId } from '../../utils/form';
import { PdsLabel } from '../_internal/pds-label/pds-label';
import { danger } from '@pine-ds/icons/icons';
Expand All @@ -10,7 +10,6 @@ import { danger } from '@pine-ds/icons/icons';
formAssociated: true
})
export class PdsInput {

/**
* Specifies if and how the browser provides `autocomplete` assistance for the field.
*/
Expand Down Expand Up @@ -85,17 +84,24 @@ export class PdsInput {

@AttachInternals() internals: ElementInternals;


private onInputEvent = (ev: Event) => {
const input = ev.target as HTMLInputElement | null;

if (input) {
this.value = input.value || '';
}

this.pdsInput.emit(ev as InputEvent);
this.internals.setFormValue(this.value);
};

if (Build.isDev == false) {
if (this.internals && typeof this.internals.setFormValue === 'function') {
this.internals.setFormValue(this.value);
}
}
}

render() {
// console.log('input internals', this.internals);
return (
<Host
aria-disabled={this.disabled ? 'true' : null}
Expand Down
8 changes: 4 additions & 4 deletions libs/core/src/components/pds-input/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
| -------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ | --------- | ----------- |
| `autocomplete` | `autocomplete` | Specifies if and how the browser provides `autocomplete` assistance for the field. | `string` | `undefined` |
| `componentId` _(required)_ | `component-id` | A unique identifier used for the underlying component `id` attribute. | `string` | `undefined` |
| `disabled` | `disabled` | Indicates whether or not the input field is disabled. | `boolean` | `undefined` |
| `disabled` | `disabled` | Determines whether or not the input field is disabled. | `boolean` | `undefined` |
| `errorMessage` | `error-message` | Specifies the error message and provides an error-themed treatment to the field. | `string` | `undefined` |
| `helperMessage` | `helper-message` | Displays a message or hint below the input field. | `string` | `undefined` |
| `invalid` | `invalid` | Indicates whether or not the input field is invalid or throws an error. | `boolean` | `undefined` |
| `invalid` | `invalid` | Determines whether or not the input field is invalid or throws an error. | `boolean` | `undefined` |
| `label` | `label` | Text to be displayed as the input label. | `string` | `undefined` |
| `name` | `name` | Specifies the name. Submitted with the form name/value pair. | `string` | `undefined` |
| `placeholder` | `placeholder` | Specifies a short hint that describes the expected value of the input field. | `string` | `undefined` |
| `readonly` | `readonly` | Indicates whether or not the input field is readonly. | `boolean` | `undefined` |
| `required` | `required` | Indicates whether or not the input field is required. | `boolean` | `undefined` |
| `readonly` | `readonly` | Determines whether or not the input field is readonly. | `boolean` | `undefined` |
| `required` | `required` | Determines whether or not the input field is required. | `boolean` | `undefined` |
| `type` | `type` | Determines the type of control that will be displayed `'email'`, `'number'`, `'password'`, `'tel'`, `'text'` | `string` | `'text'` |
| `value` | `value` | The value of the input. | `string` | `undefined` |

Expand Down
26 changes: 11 additions & 15 deletions libs/core/src/components/pds-input/test/pds-input.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('pds-input', () => {
html: `<pds-input helper-message="Use the correct syntax" component-id="field-1" value="Frank Dux"></pds-input>`
});

const helperMessage = root.shadowRoot.querySelector('.pds-input__helper-message');
const helperMessage = root?.shadowRoot?.querySelector('.pds-input__helper-message');
expect(helperMessage).not.toBeNull();
});

Expand All @@ -168,7 +168,7 @@ describe('pds-input', () => {
html: `<pds-input error-message="Please provide a helpful error message" component-id="field-1" value="Frank Dux"></pds-input>`
});

const errorMessage = root.shadowRoot.querySelector('.pds-input__error-message');
const errorMessage = root?.shadowRoot?.querySelector('.pds-input__error-message');
expect(errorMessage).not.toBeNull();
});

Expand All @@ -177,22 +177,18 @@ describe('pds-input', () => {
components: [PdsInput],
html: `<pds-input value="yada-yada" />`,
})
const pdsInput = page.root
expect(pdsInput.value).toBe('yada-yada')
const pdsInput = page.root;
expect(pdsInput?.value).toBe('yada-yada');

const input = pdsInput.shadowRoot.querySelector('input')
expect(input.value).toBe('yada-yada')
const input = pdsInput?.shadowRoot?.querySelector('input');
expect(input?.value).toBe('yada-yada');

input.value = 'yoda-yoda'
input.dispatchEvent(new Event('input'))
if (input) {
input.value = 'bang';
input.dispatchEvent(new Event('input'));
}
await page.waitForChanges()

expect(input?.value).toEqual('yoda-yoda')

input.value = '';
input.dispatchEvent(new Event('input'));
await page.waitForChanges();

expect(input?.value).toEqual('');
expect(input?.value).toEqual('bang');
})
});
11 changes: 8 additions & 3 deletions libs/core/src/components/pds-radio/pds-radio.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AttachInternals, Component, Host, h, Prop, Event, EventEmitter } from '@stencil/core';

import { AttachInternals, Build, Component, Host, h, Prop, Event, EventEmitter } from '@stencil/core';
import { assignDescription, messageId } from '../../utils/form';
import { PdsLabel } from '../_internal/pds-label/pds-label';
import { danger } from '@pine-ds/icons/icons';
Expand Down Expand Up @@ -80,7 +81,12 @@ export class PdsRadio {
const isChecked = target.checked;

this.pdsRadioChange.emit(isChecked);
this.internals.setFormValue(isChecked ? this.value : '');

if (Build.isDev == false) {
if (this.internals && typeof this.internals.setFormValue === 'function') {
this.internals.setFormValue(isChecked ? this.value : '');
}
}
}

private classNames() {
Expand All @@ -97,7 +103,6 @@ export class PdsRadio {
}

render() {
// console.log('radio internals', this.internals);
return (
<Host class={this.classNames()}>
<input
Expand Down
16 changes: 1 addition & 15 deletions libs/core/src/components/pds-radio/test/pds-radio.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,6 @@ describe('pds-radio', () => {
expect(await radio.getProperty('disabled')).toBe(true);
});

it('emits "pdsRadioChange" event when radio is checked', async () => {
const page = await newE2EPage();
await page.setContent('<pds-radio component-id="default" label="Label text" />');

const radio = await page.find('pds-radio input');
const eventSpy = await page.spyOnEvent('pdsRadioChange');
await radio.press('Space');

expect(eventSpy).toHaveReceivedEvent();
});

it('does not emit "pdsRadioChange" event when radio is clicked and disabled', async () => {
const page = await newE2EPage();
await page.setContent('<pds-radio component-id="default" label="Label text" disabled />');
Expand All @@ -68,20 +57,17 @@ describe('pds-radio', () => {
`);

const radio = await page.find('pds-radio >>> input');
const pdsRadioChangeSpy = await page.spyOnEvent('pdsRadioChange');

await radio.click();
await page.waitForChanges();

// Check form value using evaluate
const formValue = await page.evaluate(() => {
const form = document.querySelector('#myForm');
const form = document.querySelector('#myForm') as HTMLFormElement;
const formData = new FormData(form as HTMLFormElement);
return formData.get('radioGroup');
});

expect(formValue).toBe('radioValue');
expect(pdsRadioChangeSpy).toHaveReceivedEvent();
expect(pdsRadioChangeSpy.firstEvent.detail).toBeTruthy();
});
});
Loading

0 comments on commit 88b99bf

Please sign in to comment.