Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const Compact: Story = {
<obi-chevron-left-google></obi-chevron-left-google>
</obc-notification-badge-button>
<obc-number-input-field
value="10"
.value=${10}
.textAlign=${ObcNumberInputFieldTextAlign.Center}
.squared=${true}
></obc-number-input-field>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

&.hide-divider .visible-wrapper {
border-right: none;
}
}

&.corner-right {
Expand All @@ -56,6 +60,10 @@
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

&.hide-divider .visible-wrapper {
border-left: none;
}
}

&.corner-left.corner-right {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export class ObcIconButton extends LitElement {
*/
@property({type: Boolean}) hasLabel: boolean = false;

/** If false, and cornerLeft or cornerRight is true, the divider is not shown. */
@property({type: Boolean, attribute: false}) showDivider = true;

get progressSpinner() {
if (this.progress === undefined) {
return nothing;
Expand Down Expand Up @@ -202,6 +205,7 @@ export class ObcIconButton extends LitElement {
'has-label': this.hasLabel,
wide: this.wide,
progress: this.progress !== undefined,
'hide-divider': !this.showDivider,
})}
?disabled=${this.disabled}
part="wrapper"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import {
ObcNumberInputField,
ObcNumberInputFieldTextAlign,
ObcNumberInputFieldSize,
ObcNumberInputFieldInputEvent,
} from '../number-input-field/number-input-field.js';
import {parseNumberInput} from '../number-input-field/number-input-format.js';

export enum ObcKeyboardNumericType {
Floating = 'floating',
Expand Down Expand Up @@ -248,20 +250,20 @@ export class ObcKeyboardNumeric extends LitElement {
);
}

private handleInputChange(e: Event) {
private handleInput(e: ObcNumberInputFieldInputEvent) {
const input = e.target as ObcNumberInputField;
const newValue = input.value;
const newValue = input.displayValue;

if (this.validationPattern && newValue) {
const regex = new RegExp(this.validationPattern);
if (!regex.test(newValue)) {
input.value = this.value;
input.displayOverride = this.value;
return;
}
}

if (newValue && !this.isValidValue(newValue)) {
input.value = this.value;
input.displayOverride = this.value;
return;
}

Expand Down Expand Up @@ -351,14 +353,15 @@ export class ObcKeyboardNumeric extends LitElement {
return html`
<obc-number-input-field
class="input-field"
.value=${this.value}
.value=${parseNumberInput(this.value)}
.displayOverride=${this.value}
.unit=${this.unit}
.textAlign=${this.inputFieldTextAlign}
.size=${ObcNumberInputFieldSize.Large}
.helperText=${this.helperText}
?hasLeadingIcon=${this.hasLeadingIcon}
placeholder="00.0"
@input=${this.handleInputChange}
@input=${this.handleInput}
@keydown=${this.handleInputKeydown}
>
<slot name="leading-icon" slot="leading-icon"></slot>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
/* Shared base styles */
@mixin base-input-field-all;

.wrapper.align-center .label-container,
.wrapper.wrapper.align-right .label-container {
align-items: baseline;
}

/* Number input specific: text alignment */
.value-input {
text-align: right;
Expand Down Expand Up @@ -48,11 +53,15 @@
justify-content: center;
}

.wrapper .value-input {
text-align: right;
padding: 0;
}

.wrapper.align-center .value-input {
width: auto;
/* Fallback min-width for empty input - not defined in Figma design tokens */
min-width: 40px;
text-align: center;
width: var(--obc-number-input-center-width, auto);
min-width: var(--obc-number-input-center-width, 40px);
text-align: right;
flex: 0 0 auto;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import {describe, it, expect, beforeEach, vi} from 'vitest';
import './number-input-field.js';
import {ObcNumberInputField} from './number-input-field.js';
import {render} from 'vitest-browser-lit';
import {html} from 'lit';

describe('obc-number-input-field', () => {
let el: ObcNumberInputField;
let input: HTMLInputElement;

beforeEach(async () => {
const screen = render(
html`<obc-number-input-field .value=${10.2}></obc-number-input-field>`
);
el = screen.baseElement.querySelector(
'obc-number-input-field'
) as ObcNumberInputField;
await el.updateComplete;
input = el.shadowRoot!.querySelector('.value-input') as HTMLInputElement;
});

describe('value API', () => {
it('exposes value as a number', () => {
expect(el.value).toBe(10.2);
});

it('sets value to parsed number while preserving trailing decimal display', async () => {
input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

expect(el.value).toBe(10);
expect(input.value).toBe('10.');
});

it('commits trailing decimal on blur', async () => {
input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

input.blur();
await el.updateComplete;

expect(el.value).toBe(10);
expect(input.value).toBe('10');
});

it('preserves decimal when editing 10.2 to 10.', async () => {
input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

expect(input.value).toBe('10.');
});

it('preserves decimals on blur when maxFractionDigits is undefined', async () => {
input.focus();
input.value = '123.4512';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

input.blur();
await el.updateComplete;

expect(el.value).toBe(123.4512);
expect(input.value).toBe('123.4512');
});
});

describe('input event', () => {
it('dispatches input with number payload on keystroke', async () => {
const handler = vi.fn();
el.addEventListener('input', handler);

input.focus();
input.value = '12.5';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

expect(handler).toHaveBeenCalledTimes(1);
const event = handler.mock.calls[0][0] as CustomEvent<{value: number}>;
expect(event.detail.value).toBe(12.5);
});

it('dispatches input with 10 for trailing decimal input', async () => {
const handler = vi.fn();
el.addEventListener('input', handler);

input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

expect(handler).toHaveBeenCalledTimes(1);
const event = handler.mock.calls[0][0] as CustomEvent<{value: number}>;
expect(event.detail.value).toBe(10);
});
});

describe('change event', () => {
it('dispatches change on blur when committed value changed', async () => {
const handler = vi.fn();
el.addEventListener('change', handler);

input.focus();
input.value = '12.5';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

input.blur();
await el.updateComplete;

expect(handler).toHaveBeenCalledTimes(1);
const event = handler.mock.calls[0][0] as CustomEvent<{value: number}>;
expect(event.detail.value).toBe(12.5);
});

it('does not dispatch change on blur when value is unchanged', async () => {
const handler = vi.fn();
el.addEventListener('change', handler);

input.focus();
input.blur();
await el.updateComplete;

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

describe('rejectUpdatesOnFocus', () => {
beforeEach(async () => {
el.rejectUpdatesOnFocus = true;
await el.updateComplete;
});

it('keeps user-typed display when property is set while focused', async () => {
input.focus();
input.value = '99.9';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

el.value = 12.3;
await el.updateComplete;

expect(input.value).toBe('99.9');
});
});

describe('number formatting', () => {
beforeEach(async () => {
el.decimalSeparator = ',';
el.groupSeparator = ' ';
el.minFractionDigits = 2;
el.maxFractionDigits = 2;
el.value = 1234.5;
await el.updateComplete;
});

it('formats display when not focused', () => {
expect(input.value).toBe('1 234,50');
});

it('removes grouping on focus', async () => {
input.focus();
await el.updateComplete;

expect(input.value).toBe('1234,50');
});

it('shows raw input while focused', async () => {
input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

expect(input.value).toBe('10.');
});

it('formats on blur after editing', async () => {
input.focus();
input.value = '99,9';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

input.blur();
await el.updateComplete;

expect(input.value).toBe('99,90');
expect(el.value).toBe(99.9);
});

it('reformats when format properties change without focus', async () => {
el.groupSeparator = "'";
await el.updateComplete;

expect(input.value).toBe("1'234,50");
});
});

describe('rejectDuplicateUpdates', () => {
beforeEach(async () => {
el.rejectDuplicateUpdates = true;
await el.updateComplete;
});

it('keeps user-typed display when property is set to same value while focused', async () => {
input.focus();
input.value = '10.';
input.dispatchEvent(new InputEvent('input', {bubbles: true}));
await el.updateComplete;

el.value = 10.2;
await el.updateComplete;

expect(input.value).toBe('10.');
});
});
});
Loading
Loading