diff --git a/docs/app/components/snippets/the-search-2.hbs b/docs/app/components/snippets/the-search-2.hbs
index 8b08e9f59..696d7e19e 100644
--- a/docs/app/components/snippets/the-search-2.hbs
+++ b/docs/app/components/snippets/the-search-2.hbs
@@ -2,6 +2,7 @@
+ {{name}}
+
\ No newline at end of file
diff --git a/docs/app/components/snippets/the-search-8.js b/docs/app/components/snippets/the-search-8.js
new file mode 100644
index 000000000..876872c93
--- /dev/null
+++ b/docs/app/components/snippets/the-search-8.js
@@ -0,0 +1,5 @@
+import Component from '@glimmer/component';
+
+export default class extends Component {
+ diacritics = ['María', 'Søren Larsen', 'João', 'Saša Jurić', 'Íñigo'];
+}
diff --git a/docs/app/components/snippets/the-search-9.hbs b/docs/app/components/snippets/the-search-9.hbs
new file mode 100644
index 000000000..ebc063c25
--- /dev/null
+++ b/docs/app/components/snippets/the-search-9.hbs
@@ -0,0 +1,9 @@
+
+ {{name}}
+
\ No newline at end of file
diff --git a/docs/app/components/snippets/the-search-9.js b/docs/app/components/snippets/the-search-9.js
new file mode 100644
index 000000000..876872c93
--- /dev/null
+++ b/docs/app/components/snippets/the-search-9.js
@@ -0,0 +1,5 @@
+import Component from '@glimmer/component';
+
+export default class extends Component {
+ diacritics = ['María', 'Søren Larsen', 'João', 'Saša Jurić', 'Íñigo'];
+}
diff --git a/docs/app/controllers/public-pages/docs/the-search.js b/docs/app/controllers/public-pages/docs/the-search.js
index 9a0c4c7f0..6d468bdf4 100644
--- a/docs/app/controllers/public-pages/docs/the-search.js
+++ b/docs/app/controllers/public-pages/docs/the-search.js
@@ -6,6 +6,8 @@ import TheSearch4 from '../../../components/snippets/the-search-4';
import TheSearch5 from '../../../components/snippets/the-search-5';
import TheSearch6 from '../../../components/snippets/the-search-6';
import TheSearch7 from '../../../components/snippets/the-search-7';
+import TheSearch8 from '../../../components/snippets/the-search-8';
+import TheSearch9 from '../../../components/snippets/the-search-9';
export default class TheSearch extends Controller {
theSearch1 = TheSearch1;
@@ -15,6 +17,8 @@ export default class TheSearch extends Controller {
theSearch5 = TheSearch5;
theSearch6 = TheSearch6;
theSearch7 = TheSearch7;
+ theSearch8 = TheSearch8;
+ theSearch9 = TheSearch9;
names = [
'Stefan',
'Miguel',
diff --git a/docs/app/templates/public-pages/docs/api-reference.hbs b/docs/app/templates/public-pages/docs/api-reference.hbs
index 759682215..1ed0f456d 100644
--- a/docs/app/templates/public-pages/docs/api-reference.hbs
+++ b/docs/app/templates/public-pages/docs/api-reference.hbs
@@ -301,6 +301,11 @@
string
When the options are objects and no custom matches function is provided, this option tells the component what property of the options should the default matches use to filter
+
+ searchFieldPosition
+ string
+ Allows to change the position of search field. Possible values: 'before-options' or 'trigger'. Default is single is before-options, default for multiple is trigger
+
searchMessage
string
diff --git a/docs/app/templates/public-pages/docs/the-search.hbs b/docs/app/templates/public-pages/docs/the-search.hbs
index 3af50982a..d156a0344 100644
--- a/docs/app/templates/public-pages/docs/the-search.hbs
+++ b/docs/app/templates/public-pages/docs/the-search.hbs
@@ -25,6 +25,22 @@
{{component (ensure-safe-component this.theSearch2)}}
+Search field position
+
+
+ The default search field position for single select is inside the dropdown and only visible when the dropdown is open (@searchFieldPosition="before-options"
).
+ In multiple selection you will find the search field inside trigger box (@searchFieldPosition="trigger"
).
+ By passing @searchFieldPosition
you can change this logic for single and multiple selection.
+
+
+
+ {{component (ensure-safe-component this.theSearch8)}}
+
+
+
+ {{component (ensure-safe-component this.theSearch9)}}
+
+
Customize the search field
diff --git a/ember-power-select/less/base.less b/ember-power-select/less/base.less
index b5d2d5dd7..e8b72d31e 100644
--- a/ember-power-select/less/base.less
+++ b/ember-power-select/less/base.less
@@ -24,6 +24,14 @@
display:table;
clear:both;
}
+
+ .ember-power-select-input {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
}
.ember-power-select-trigger:focus,
.ember-power-select-trigger--active {
@@ -152,6 +160,19 @@
}
}
}
+.ember-power-select-search-input-field {
+ width: 100%;
+ height: 100%;
+ padding: 0 8px;
+ font-family: inherit;
+ font-size: inherit;
+ border: none;
+ display: block;
+ line-height: inherit;
+ -webkit-appearance: none;
+ outline: none;
+ background-color: transparent;
+}
// Dropdown
.ember-power-select-dropdown {
diff --git a/ember-power-select/package.json b/ember-power-select/package.json
index 2ed08446f..5f66469f8 100644
--- a/ember-power-select/package.json
+++ b/ember-power-select/package.json
@@ -164,6 +164,7 @@
"./components/power-select-multiple/trigger.js": "./dist/_app_/components/power-select-multiple/trigger.js",
"./components/power-select.js": "./dist/_app_/components/power-select.js",
"./components/power-select/before-options.js": "./dist/_app_/components/power-select/before-options.js",
+ "./components/power-select/input.js": "./dist/_app_/components/power-select/input.js",
"./components/power-select/label.js": "./dist/_app_/components/power-select/label.js",
"./components/power-select/no-matches-message.js": "./dist/_app_/components/power-select/no-matches-message.js",
"./components/power-select/options.js": "./dist/_app_/components/power-select/options.js",
diff --git a/ember-power-select/scss/base.scss b/ember-power-select/scss/base.scss
index 2a88a1f0d..d5cf704e9 100644
--- a/ember-power-select/scss/base.scss
+++ b/ember-power-select/scss/base.scss
@@ -28,6 +28,14 @@
display:table;
clear:both;
}
+
+ .ember-power-select-input {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ }
}
.ember-power-select-trigger:focus,
.ember-power-select-trigger--active {
@@ -155,6 +163,19 @@
}
}
}
+.ember-power-select-search-input-field {
+ width: 100%;
+ height: 100%;
+ padding: 0 8px;
+ font-family: inherit;
+ font-size: inherit;
+ border: none;
+ display: block;
+ line-height: inherit;
+ -webkit-appearance: none;
+ outline: none;
+ background-color: transparent;
+}
// Dropdown
.ember-power-select-dropdown {
diff --git a/ember-power-select/src/components/power-select-multiple.hbs b/ember-power-select/src/components/power-select-multiple.hbs
index a7504ec04..196b8b978 100644
--- a/ember-power-select/src/components/power-select-multiple.hbs
+++ b/ember-power-select/src/components/power-select-multiple.hbs
@@ -11,7 +11,7 @@
@labelComponent={{ensure-safe-component @labelComponent}}
@afterOptionsComponent={{ensure-safe-component @afterOptionsComponent}}
@allowClear={{@allowClear}}
- @beforeOptionsComponent={{if @beforeOptionsComponent (ensure-safe-component @beforeOptionsComponent) null}}
+ @beforeOptionsComponent={{if @beforeOptionsComponent (ensure-safe-component @beforeOptionsComponent)}}
@buildSelection={{or @buildSelection this.defaultBuildSelection}}
@calculatePosition={{@calculatePosition}}
@closeOnSelect={{@closeOnSelect}}
@@ -50,6 +50,7 @@
@search={{@search}}
@searchEnabled={{@searchEnabled}}
@searchField={{@searchField}}
+ @searchFieldPosition={{or @searchFieldPosition 'trigger'}}
@searchMessage={{@searchMessage}}
@searchMessageComponent={{@searchMessageComponent}}
@searchPlaceholder={{@searchPlaceholder}}
diff --git a/ember-power-select/src/components/power-select-multiple/trigger.hbs b/ember-power-select/src/components/power-select-multiple/trigger.hbs
index 33030452f..5f3dfe48a 100644
--- a/ember-power-select/src/components/power-select-multiple/trigger.hbs
+++ b/ember-power-select/src/components/power-select-multiple/trigger.hbs
@@ -52,7 +52,7 @@
{{/if}}
{{/each}}
- {{#if @searchEnabled}}
+ {{#if (and @searchEnabled (eq @searchFieldPosition 'trigger'))}}
{{#let
(component
diff --git a/ember-power-select/src/components/power-select-multiple/trigger.ts b/ember-power-select/src/components/power-select-multiple/trigger.ts
index 181b0cc5f..5656da2d9 100644
--- a/ember-power-select/src/components/power-select-multiple/trigger.ts
+++ b/ember-power-select/src/components/power-select-multiple/trigger.ts
@@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { action } from '@ember/object';
import { get } from '@ember/object';
import { scheduleTask } from 'ember-lifeline';
-import type { Select } from '../power-select';
+import type { Select, TSearchFieldPosition } from '../power-select';
import type { ComponentLike } from '@glint/template';
import { modifier } from 'ember-modifier';
import { deprecate } from '@ember/debug';
@@ -14,6 +14,7 @@ interface PowerSelectMultipleTriggerSignature {
searchEnabled: boolean;
placeholder?: string;
searchField: string;
+ searchFieldPosition?: TSearchFieldPosition;
listboxId?: string;
tabindex?: string;
ariaLabel?: string;
diff --git a/ember-power-select/src/components/power-select.hbs b/ember-power-select/src/components/power-select.hbs
index 42e4baab8..e94033552 100644
--- a/ember-power-select/src/components/power-select.hbs
+++ b/ember-power-select/src/components/power-select.hbs
@@ -85,6 +85,7 @@
@select={{publicAPI}}
@searchEnabled={{@searchEnabled}}
@searchField={{@searchField}}
+ @searchFieldPosition={{this.searchFieldPosition}}
@onFocus={{this.handleFocus}}
@onBlur={{this.handleBlur}}
@extra={{@extra}}
@@ -133,6 +134,7 @@
@ariaActiveDescendant={{if this.highlightedIndex (concat publicAPI.uniqueId "-" this.highlightedIndex)}}
@selectedItemComponent={{ensure-safe-component @selectedItemComponent}}
@searchPlaceholder={{@searchPlaceholder}}
+ @searchFieldPosition={{this.searchFieldPosition}}
@ariaLabel={{@ariaLabel}}
@ariaLabelledBy={{this.ariaLabelledBy}}
@ariaDescribedBy={{@ariaDescribedBy}}
diff --git a/ember-power-select/src/components/power-select.ts b/ember-power-select/src/components/power-select.ts
index bda794020..3083c5298 100644
--- a/ember-power-select/src/components/power-select.ts
+++ b/ember-power-select/src/components/power-select.ts
@@ -91,6 +91,7 @@ export interface PowerSelectArgs {
animationEnabled?: boolean;
tabindex?: number | string;
searchPlaceholder?: string;
+ searchFieldPosition?: TSearchFieldPosition;
verticalPosition?: string;
horizontalPosition?: string;
triggerId?: string;
@@ -134,6 +135,7 @@ export interface PowerSelectArgs {
}
export type TLabelClickAction = 'focus' | 'open';
+export type TSearchFieldPosition = 'before-options' | 'trigger';
export interface PowerSelectSignature {
Element: HTMLElement;
@@ -368,6 +370,12 @@ export default class PowerSelectComponent extends Component
{{!-- template-lint-disable require-input-label --}}
false | void;
diff --git a/ember-power-select/src/components/power-select/input.hbs b/ember-power-select/src/components/power-select/input.hbs
new file mode 100644
index 000000000..8373cb8ab
--- /dev/null
+++ b/ember-power-select/src/components/power-select/input.hbs
@@ -0,0 +1,27 @@
+
+ {{!-- template-lint-disable require-input-label --}}
+
+
\ No newline at end of file
diff --git a/ember-power-select/src/components/power-select/input.ts b/ember-power-select/src/components/power-select/input.ts
new file mode 100644
index 000000000..b2923bb40
--- /dev/null
+++ b/ember-power-select/src/components/power-select/input.ts
@@ -0,0 +1,87 @@
+import Component from '@glimmer/component';
+import { runTask } from 'ember-lifeline';
+import { action } from '@ember/object';
+import { modifier } from 'ember-modifier';
+import type { Select, TSearchFieldPosition } from '../power-select';
+
+interface PowerSelectInputSignature {
+ Element: HTMLElement;
+ Args: {
+ select: Select;
+ ariaLabel?: string;
+ ariaLabelledBy?: string;
+ ariaDescribedBy?: string;
+ role?: string;
+ searchPlaceholder?: string;
+ searchFieldPosition?: TSearchFieldPosition;
+ ariaActiveDescendant?: string;
+ listboxId?: string;
+ onKeydown: (e: KeyboardEvent) => false | void;
+ onBlur: (e: FocusEvent) => void;
+ onFocus: (e: FocusEvent) => void;
+ onInput: (e: InputEvent) => boolean;
+ autofocus?: boolean;
+ };
+}
+
+export default class PowerSelectInput extends Component {
+ didSetup: boolean = false;
+
+ @action
+ handleKeydown(e: KeyboardEvent): false | void {
+ if (this.args.onKeydown(e) === false) {
+ return false;
+ }
+ if (e.keyCode === 13) {
+ this.args.select.actions.close(e);
+ }
+ }
+
+ @action
+ handleInput(event: Event): false | void {
+ const e = event as InputEvent;
+ if (this.args.onInput(e) === false) {
+ return false;
+ }
+ }
+
+ @action
+ handleBlur(event: Event) {
+ if (this.args.searchFieldPosition === 'trigger') {
+ this.args.select.actions?.search('');
+ }
+
+ this.args.onBlur(event as FocusEvent);
+ }
+
+ setupInput = modifier(
+ (el: HTMLElement) => {
+ if (this.didSetup) {
+ return;
+ }
+
+ this.didSetup = true;
+
+ this._focusInput(el);
+
+ return () => {
+ this.args.select.actions?.search('');
+ };
+ },
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ { eager: false },
+ );
+
+ private _focusInput(el: HTMLElement) {
+ runTask(
+ this,
+ () => {
+ if (this.args.autofocus !== false) {
+ el.focus();
+ }
+ },
+ 0,
+ );
+ }
+}
diff --git a/ember-power-select/src/components/power-select/trigger.hbs b/ember-power-select/src/components/power-select/trigger.hbs
index adf1143cd..f583271bd 100644
--- a/ember-power-select/src/components/power-select/trigger.hbs
+++ b/ember-power-select/src/components/power-select/trigger.hbs
@@ -1,24 +1,77 @@
{{#if (ember-power-select-is-selected-present @select.selected)}}
- {{#if @selectedItemComponent}}
- {{#let (component (ensure-safe-component @selectedItemComponent)) as |SelectedItemComponent|}}
-
- {{/let}}
- {{else}}
- {{yield @select.selected @select}}
+ {{#if (or (not-eq @searchFieldPosition 'trigger') (not @select.searchText))}}
+ {{#if @selectedItemComponent}}
+ {{#let (component (ensure-safe-component @selectedItemComponent)) as |SelectedItemComponent|}}
+
+ {{/let}}
+ {{else}}
+ {{yield @select.selected @select}}
+ {{/if}}
+ {{/if}}
+ {{#if (and @searchEnabled (eq @searchFieldPosition 'trigger'))}}
+
{{/if}}
{{#if (and @allowClear (not @select.disabled))}}
{{!-- template-lint-disable no-pointer-down-event-binding --}}
×
{{/if}}
{{else}}
- {{#let (component (ensure-safe-component @placeholderComponent)) as |PlaceholderComponent|}}
-
- {{/let}}
+ {{#if (and @searchEnabled (eq @searchFieldPosition 'trigger'))}}
+ {{#let
+ (component
+ "power-select/input"
+ select=@select
+ ariaActiveDescendant=@ariaActiveDescendant
+ ariaLabelledBy=@ariaLabelledBy
+ ariaDescribedBy=@ariaDescribedBy
+ role=@role
+ ariaLabel=@ariaLabel
+ listboxId=@listboxId
+ searchPlaceholder=@placeholder
+ onFocus=@onFocus
+ onBlur=@onBlur
+ onKeydown=@onKeydown
+ onInput=@onInput
+ searchFieldPosition=@searchFieldPosition
+ autofocus=false
+ )
+ as |InputComponent|
+ }}
+ {{#let (component (ensure-safe-component @placeholderComponent)) as |PlaceholderComponent|}}
+
+ {{/let}}
+ {{/let}}
+ {{else}}
+ {{#let (component (ensure-safe-component @placeholderComponent)) as |PlaceholderComponent|}}
+
+ {{/let}}
+ {{/if}}
{{/if}}
diff --git a/ember-power-select/src/components/power-select/trigger.ts b/ember-power-select/src/components/power-select/trigger.ts
index 0934361a1..006fb42d8 100644
--- a/ember-power-select/src/components/power-select/trigger.ts
+++ b/ember-power-select/src/components/power-select/trigger.ts
@@ -1,6 +1,6 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
-import type { Select } from '../power-select';
+import type { Select, TSearchFieldPosition } from '../power-select';
import type { ComponentLike } from '@glint/template';
interface PowerSelectTriggerSignature {
@@ -8,10 +8,24 @@ interface PowerSelectTriggerSignature {
Args: {
select: Select;
allowClear: boolean;
- extra: any;
+ searchEnabled: boolean;
placeholder?: string;
+ searchField: string;
+ searchFieldPosition?: TSearchFieldPosition;
+ listboxId?: string;
+ tabindex?: string;
+ ariaLabel?: string;
+ ariaLabelledBy?: string;
+ ariaDescribedBy?: string;
+ role?: string;
+ ariaActiveDescendant: string;
+ extra?: any;
placeholderComponent?: string | ComponentLike;
selectedItemComponent?: string | ComponentLike;
+ onInput?: (e: InputEvent) => boolean;
+ onKeydown?: (e: KeyboardEvent) => boolean;
+ onFocus?: (e: FocusEvent) => void;
+ onBlur?: (e: FocusEvent) => void;
};
Blocks: {
default: [selected: any, select: Select];
diff --git a/test-app/tests/integration/components/power-select/general-behaviour-test.js b/test-app/tests/integration/components/power-select/general-behaviour-test.js
index cc9b0d957..fbf27133f 100644
--- a/test-app/tests/integration/components/power-select/general-behaviour-test.js
+++ b/test-app/tests/integration/components/power-select/general-behaviour-test.js
@@ -126,6 +126,27 @@ module(
.exists('The search box is rendered');
});
+ test('The search box position is inside the trigger by passing `@searchFieldPosition="trigger"`', async function (assert) {
+ assert.expect(3);
+
+ this.numbers = numbers;
+ await render(hbs`
+
+ {{option}}
+
+ `);
+
+ assert
+ .dom('.ember-power-select-trigger input[type="search"]')
+ .exists('The search box is rendered in trigger');
+
+ await clickTrigger();
+ assert.dom('.ember-power-select-dropdown').exists('Dropdown is rendered');
+ assert
+ .dom('.ember-power-select-search')
+ .doesNotExist('The search box does not exists in dropdown');
+ });
+
test("The search box shouldn't gain focus if autofocus is disabled", async function (assert) {
assert.expect(1);
this.numbers = numbers;
diff --git a/test-app/tests/integration/components/power-select/multiple-test.js b/test-app/tests/integration/components/power-select/multiple-test.js
index 1ccbef067..cdb56515a 100644
--- a/test-app/tests/integration/components/power-select/multiple-test.js
+++ b/test-app/tests/integration/components/power-select/multiple-test.js
@@ -55,6 +55,19 @@ module(
assert.dom('.ember-power-select-dropdown input').doesNotExist();
});
+ test('Multiple selects have a search box in the dropdown when the search is enabled and search position is `after-options`', async function (assert) {
+ assert.expect(2);
+ this.numbers = numbers;
+ await render(hbs`
+
+ {{option}}
+
+ `);
+ await clickTrigger();
+ assert.dom('.ember-power-select-trigger input').doesNotExist();
+ assert.dom('.ember-power-select-dropdown input').exists();
+ });
+
test('The searchbox of multiple selects has type="search" and a form attribute to prevent submitting the wrapper form when pressing enter', async function (assert) {
assert.expect(2);
@@ -86,6 +99,18 @@ module(
assert.dom('.ember-power-select-trigger-multiple-input').isFocused();
});
+ test('When the select opens and search position is `after-options`, the search input (if any) in the dropdown gets the focus', async function (assert) {
+ assert.expect(1);
+ this.numbers = numbers;
+ await render(hbs`
+
+ {{option}}
+
+ `);
+ await clickTrigger();
+ assert.dom('.ember-power-select-search-input').isFocused();
+ });
+
test("Click on an element selects it and closes the dropdown and focuses the trigger's input", async function (assert) {
assert.expect(4);