mirror of
https://github.com/Choices-js/Choices.git
synced 2026-03-14 22:55:46 +01:00
Merge 1e7f7ec4e3 into 095ecb3589
This commit is contained in:
commit
bfdb93b2f8
6 changed files with 195 additions and 22 deletions
|
|
@ -1253,6 +1253,44 @@
|
|||
</script>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="input-width-placeholder">
|
||||
<label for="choices-input-width-placeholder">Input width with CJK placeholder</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-input-width-placeholder"
|
||||
id="choices-input-width-placeholder"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
</select>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new Choices('#choices-input-width-placeholder', {
|
||||
searchPlaceholderValue: '搜索',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="input-width-typing">
|
||||
<label for="choices-input-width-typing">Input width while typing</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-input-width-typing"
|
||||
id="choices-input-width-typing"
|
||||
multiple
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
</select>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new Choices('#choices-input-width-typing', {});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1205,6 +1205,42 @@
|
|||
</script>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="input-width-placeholder">
|
||||
<label for="choices-input-width-placeholder">Input width with CJK placeholder</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-input-width-placeholder"
|
||||
id="choices-input-width-placeholder"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
</select>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new Choices('#choices-input-width-placeholder', {
|
||||
searchPlaceholderValue: '搜索',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div data-test-hook="input-width-typing">
|
||||
<label for="choices-input-width-typing">Input width while typing</label>
|
||||
<select
|
||||
class="form-control"
|
||||
name="choices-input-width-typing"
|
||||
id="choices-input-width-typing"
|
||||
>
|
||||
<option value="Choice 1">Choice 1</option>
|
||||
<option value="Choice 2">Choice 2</option>
|
||||
</select>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new Choices('#choices-input-width-typing', {});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { ClassNames } from '../interfaces/class-names';
|
||||
import { PassedElementType, PassedElementTypes } from '../interfaces/passed-element-type';
|
||||
import { PassedElementType } from '../interfaces/passed-element-type';
|
||||
import { addClassesToElement } from '../lib/utils';
|
||||
|
||||
export default class Input {
|
||||
element: HTMLInputElement;
|
||||
|
|
@ -106,14 +107,45 @@ export default class Input {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the correct input width based on placeholder
|
||||
* value or input value
|
||||
* Set the correct input width based on placeholder value or input value.
|
||||
* Renders text into a hidden off-screen span that inherits the input's
|
||||
* CSS classes and measures its pixel width, then converts to `ch` units.
|
||||
* This correctly handles CJK, Hangul, fullwidth forms, emoji, and any
|
||||
* font — no hard-coded code-point ranges required.
|
||||
*/
|
||||
setWidth(): void {
|
||||
// Resize input to contents or placeholder
|
||||
const { element } = this;
|
||||
element.style.minWidth = `${element.placeholder.length + 1}ch`;
|
||||
element.style.width = `${element.value.length + 1}ch`;
|
||||
const { value, placeholder } = element;
|
||||
let minWidth = 0;
|
||||
let width = 0;
|
||||
|
||||
if (value || placeholder) {
|
||||
const e = document.createElement('span');
|
||||
e.style.position = 'absolute';
|
||||
e.style.visibility = 'hidden';
|
||||
e.style.whiteSpace = 'pre';
|
||||
e.style.height = 'auto';
|
||||
e.style.width = 'auto';
|
||||
e.style.minWidth = '1ch';
|
||||
addClassesToElement(e, Array.from(element.classList));
|
||||
element.after(e);
|
||||
const chInPx = e.clientWidth;
|
||||
|
||||
if (placeholder) {
|
||||
e.innerText = placeholder;
|
||||
minWidth = e.clientWidth / chInPx;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
e.innerText = value;
|
||||
width = e.clientWidth / chInPx;
|
||||
}
|
||||
|
||||
e.remove();
|
||||
}
|
||||
|
||||
element.style.minWidth = `${Math.ceil(minWidth) + 1}ch`;
|
||||
element.style.width = `${Math.ceil(width) + 1}ch`;
|
||||
}
|
||||
|
||||
setActiveDescendant(activeDescendantID: string): void {
|
||||
|
|
@ -125,9 +157,7 @@ export default class Input {
|
|||
}
|
||||
|
||||
_onInput(): void {
|
||||
if (this.type !== PassedElementTypes.SelectOne) {
|
||||
this.setWidth();
|
||||
}
|
||||
this.setWidth();
|
||||
}
|
||||
|
||||
_onPaste(event: ClipboardEvent): void {
|
||||
|
|
|
|||
|
|
@ -1153,5 +1153,46 @@ describe(`Choices - select multiple`, () => {
|
|||
await suite.expectChoiceCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input width', () => {
|
||||
describe('placeholder', () => {
|
||||
const testId = 'input-width-placeholder';
|
||||
|
||||
// CI headless environments may not have CJK fonts installed, causing the
|
||||
// DOM measurement to return 0px for CJK chars. Just verify the style is
|
||||
// set in ch units — not the exact numeric value.
|
||||
test('sets minWidth from CJK placeholder', async ({ page, bundle }) => {
|
||||
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
|
||||
await suite.start();
|
||||
|
||||
const minWidth = await suite.input.evaluate((el) => (el as HTMLInputElement).style.minWidth);
|
||||
expect(minWidth).toMatch(/^\d+ch$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('typing', () => {
|
||||
const testId = 'input-width-typing';
|
||||
|
||||
test('sets width for English character', async ({ page, bundle }) => {
|
||||
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
|
||||
await suite.startWithClick();
|
||||
await suite.typeText('f');
|
||||
await expect(suite.input).toHaveValue('f');
|
||||
|
||||
// The exact ch value depends on font metrics in the test environment.
|
||||
// Any typed character produces Math.ceil(px/chPx)+1 >= 2.
|
||||
const width = await suite.input.evaluate((el) => (el as HTMLInputElement).style.width);
|
||||
expect(width).toMatch(/^\d+ch$/);
|
||||
expect(parseInt(width, 10)).toBeGreaterThanOrEqual(2);
|
||||
await suite.startWithClick();
|
||||
await suite.typeText('中');
|
||||
await expect(suite.input).toHaveValue('中');
|
||||
|
||||
const width = await suite.input.evaluate((el) => (el as HTMLInputElement).style.width);
|
||||
expect(width).toMatch(/^\d+ch$/);
|
||||
expect(parseInt(width, 10)).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1026,5 +1026,44 @@ describe(`Choices - select one`, () => {
|
|||
await suite.expectChoiceCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input width', () => {
|
||||
describe('placeholder', () => {
|
||||
const testId = 'input-width-placeholder';
|
||||
|
||||
// For select-one the search input lives inside the dropdown and setWidth
|
||||
// is only triggered by typing, not on open. Verify the placeholder
|
||||
// attribute is correctly applied to the search input instead.
|
||||
test('sets search placeholder on input when dropdown opens', async ({ page, bundle }) => {
|
||||
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
|
||||
await suite.startWithClick();
|
||||
|
||||
const placeholder = await suite.input.evaluate((el) => (el as HTMLInputElement).placeholder);
|
||||
expect(placeholder).toEqual('搜索');
|
||||
});
|
||||
});
|
||||
|
||||
describe('typing', () => {
|
||||
const testId = 'input-width-typing';
|
||||
|
||||
test('sets width for English character', async ({ page, bundle }) => {
|
||||
const suite = new SelectTestSuit(page, bundle, testUrl, testId);
|
||||
await suite.startWithClick();
|
||||
await suite.typeText('f');
|
||||
await expect(suite.input).toHaveValue('f');
|
||||
|
||||
const width = await suite.input.evaluate((el) => (el as HTMLInputElement).style.width);
|
||||
expect(width).toMatch(/^\d+ch$/);
|
||||
expect(parseInt(width, 10)).toBeGreaterThanOrEqual(2);
|
||||
await suite.startWithClick();
|
||||
await suite.typeText('中');
|
||||
await expect(suite.input).toHaveValue('中');
|
||||
|
||||
const width = await suite.input.evaluate((el) => (el as HTMLInputElement).style.width);
|
||||
expect(width).toMatch(/^\d+ch$/);
|
||||
expect(parseInt(width, 10)).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -84,10 +84,10 @@ describe('components/input', () => {
|
|||
});
|
||||
|
||||
describe('when element is select one', () => {
|
||||
it('does not set input width', () => {
|
||||
it('sets input width', () => {
|
||||
instance.type = 'select-one';
|
||||
instance._onInput();
|
||||
expect(setWidthStub.callCount).to.equal(0);
|
||||
expect(setWidthStub.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -261,17 +261,6 @@ describe('components/input', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setWidth', () => {
|
||||
it('sets the width of the element based on input value and placeholder', () => {
|
||||
instance.placeholder = 'This is a placeholder';
|
||||
instance.element.value = 'This is a value';
|
||||
expect(instance.element.style.width).to.not.equal('16ch');
|
||||
instance.setWidth();
|
||||
expect(instance.element.style.width).to.equal('16ch');
|
||||
expect(instance.element.style.minWidth).to.equal('22ch');
|
||||
});
|
||||
});
|
||||
|
||||
describe('placeholder setter', () => {
|
||||
it('sets value of element to passed placeholder', () => {
|
||||
const placeholder = 'test';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue