Skip to content
Open
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
5 changes: 1 addition & 4 deletions packages/components-dev/tag/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,7 @@ export class DevDocsExamples {}
templateUrl: 'template.html',
styleUrls: ['styles.scss'],
providers: [
// {
// provide: KBQ_TAGS_DEFAULT_OPTIONS,
// useValue: { separatorKeyCodes: [ENTER], addOnPaste: false } satisfies KbqTagsDefaultOptions
// }
// kbqTagsDefaultOptionsProvider({ separatorKeyCodes: [ENTER], addOnPaste: false })
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
Expand Down
52 changes: 52 additions & 0 deletions packages/components/tags/e2e.playwright-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,56 @@ test.describe('KbqTagModule', () => {
await expect(getComponent(page)).toHaveScreenshot('05-dark.png');
});
});

test.describe('E2eTagInputSeparators', () => {
test.use({ permissions: ['clipboard-read', 'clipboard-write'] });

const getComponent = (page: Page): Locator => page.getByTestId('e2eTagInputSeparators');
const getInput = (page: Page): Locator => page.getByTestId('e2eTagInputSeparatorsInput');
const getTags = (component: Locator): Locator => component.locator('kbq-tag');

const pasteFromClipboard = async (page: Page, target: Locator, text: string) => {
await page.evaluate((value) => navigator.clipboard.writeText(value), text);
await target.click();
await page.keyboard.press('ControlOrMeta+v');
};

test('creates a tag on Enter while typing', async ({ page }) => {
await page.goto('/E2eTagInputSeparators');
const input = getInput(page);

await input.fill('typed-tag');
await input.press('Enter');

await expect(getTags(getComponent(page))).toHaveText(['typed-tag']);
});

test('does not end the tag on Space while typing, since it is a paste-only separator', async ({ page }) => {
await page.goto('/E2eTagInputSeparators');
const input = getInput(page);

await input.pressSequentially('two words');

await expect(getTags(getComponent(page))).toHaveCount(0);
await expect(input).toHaveValue('two words');
});

test('splits pasted text on whitespace, via the keyless `appliesTo: paste` separator', async ({ page }) => {
await page.goto('/E2eTagInputSeparators');
const input = getInput(page);

await pasteFromClipboard(page, input, 'alpha beta\tgamma\ndelta');

await expect(getTags(getComponent(page))).toHaveText(['alpha', 'beta', 'gamma', 'delta']);
});

test('splits pasted text on Enter, a separator shared by both contexts', async ({ page }) => {
await page.goto('/E2eTagInputSeparators');
const input = getInput(page);

await pasteFromClipboard(page, input, 'first\nsecond');

await expect(getTags(getComponent(page))).toHaveText(['first', 'second']);
});
});
});
57 changes: 54 additions & 3 deletions packages/components/tags/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChangeDetectionStrategy, Component, model } from '@angular/core';
import { ChangeDetectionStrategy, Component, model, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KbqAutocompleteModule } from '@koobiq/components/autocomplete';
import { KbqComponentColors } from '@koobiq/components/core';
import { ENTER, KbqComponentColors } from '@koobiq/components/core';
import { KbqIconModule } from '@koobiq/components/icon';
import { KbqInputModule } from '@koobiq/components/input';
import { KbqTagsModule } from '@koobiq/components/tags';
import { KbqTagInputEvent, KbqTagsModule, kbqTagsDefaultOptionsProvider } from '@koobiq/components/tags';

@Component({
selector: 'e2e-tag-state-and-style',
Expand Down Expand Up @@ -353,3 +353,54 @@ export class E2eTagInputStates {}
}
})
export class E2eTagAutocompleteStates {}

/**
* Demonstrates `KbqTagSeparator.appliesTo` scoping: `Enter` creates a tag both while typing and
* on paste, while a keyless whitespace separator (`/\s+/`) only splits pasted text — typing a
* space does not end the current tag.
*/
@Component({
selector: 'e2e-tag-input-separators',
imports: [KbqTagsModule, KbqInputModule],
template: `
<kbq-form-field>
<kbq-tag-list #tagList="kbqTagList">
@for (tag of tags(); track tag) {
<kbq-tag>{{ tag }}</kbq-tag>
}

<input
data-testid="e2eTagInputSeparatorsInput"
autocomplete="off"
kbqInput
placeholder="New tag"
[kbqTagInputFor]="tagList"
(kbqTagInputTokenEnd)="add($event)"
/>
</kbq-tag-list>
</kbq-form-field>
`,
providers: [
kbqTagsDefaultOptionsProvider({
separatorKeyCodes: [ENTER],
separators: [
{ symbol: /\r?\n/, key: 'Enter', keyCode: ENTER, appliesTo: ['input', 'paste'] },
{ symbol: /\s+/, appliesTo: ['paste'] }
]
})
],
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'data-testid': 'e2eTagInputSeparators'
}
})
export class E2eTagInputSeparators {
readonly tags = signal<string[]>([]);

add({ input, value }: KbqTagInputEvent): void {
if (value) {
this.tags.update((tags) => [...tags, value]);
input.value = '';
}
}
}
1 change: 0 additions & 1 deletion packages/components/tags/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './tag-default-options';
export * from './tag-input';
export * from './tag-list.component';
export * from './tag.component';
Expand Down
13 changes: 0 additions & 13 deletions packages/components/tags/tag-default-options.ts

This file was deleted.

25 changes: 25 additions & 0 deletions packages/components/tags/tag-input.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ To enable tag reordering, you need to set the `draggable` property for `kbq-tag-

<!-- example(tag-input-draggable) -->

### Separators

By default, a tag is created on `Enter`. Additional separator keys are set via `kbqTagInputSeparatorKeyCodes` and apply both while typing and when pasting from the clipboard.

If a separator should only apply on paste (e.g. a space, which is a common character inside a tag), mark it with `appliesTo: ['paste']`. Separators without a `key` (e.g. `/\s+/` for any run of whitespace) are paste-only by default, since no keystroke can ever match them.

Application- or module-wide defaults are set via `kbqTagsDefaultOptionsProvider`:

```ts
import { ENTER } from '@koobiq/components/core';
import { kbqTagsDefaultOptionsProvider } from '@koobiq/components/tags';

@NgModule({
providers: [
kbqTagsDefaultOptionsProvider({
separatorKeyCodes: [ENTER],
separators: [
{ symbol: /\r?\n/, key: 'Enter', keyCode: ENTER, appliesTo: ['input', 'paste'] },
{ symbol: /\s+/, appliesTo: ['paste'] } // paste-only — splits on any whitespace
]
})
]
})
```

### Keyboard navigation

#### Focus in empty input area
Expand Down
25 changes: 25 additions & 0 deletions packages/components/tags/tag-input.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@

<!-- example(tag-input-draggable) -->

### Разделители

По умолчанию тег создаётся по `Enter`. Дополнительные клавиши-разделители задаются через `kbqTagInputSeparatorKeyCodes` и работают как при наборе текста, так и при вставке из буфера обмена.

Если разделитель нужен только при вставке (например, пробел — обычный символ внутри тега), укажите для него `appliesTo: ['paste']`. Разделители без `key` (например, `/\s+/` для любой последовательности пробельных символов) вставочные по умолчанию, так как им не может соответствовать нажатие клавиши.

Настройки по умолчанию для приложения или модуля задаются через `kbqTagsDefaultOptionsProvider`:

```ts
import { ENTER } from '@koobiq/components/core';
import { kbqTagsDefaultOptionsProvider } from '@koobiq/components/tags';

@NgModule({
providers: [
kbqTagsDefaultOptionsProvider({
separatorKeyCodes: [ENTER],
separators: [
{ symbol: /\r?\n/, key: 'Enter', keyCode: ENTER, appliesTo: ['input', 'paste'] },
{ symbol: /\s+/, appliesTo: ['paste'] } // только при вставке — разделяет по любым пробельным символам
]
})
]
})
```

### Навигация с клавиатуры

#### Фокус в пустой области ввода
Expand Down
Loading