@koobiq/components 19.6.0 → 19.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/breadcrumbs/breadcrumbs.scss +0 -4
  2. package/code-block/code-block-highlight.d.ts +1 -0
  3. package/core/option/option.d.ts +8 -0
  4. package/core/validation/validation.d.ts +1 -1
  5. package/fesm2022/koobiq-components-breadcrumbs.mjs +2 -2
  6. package/fesm2022/koobiq-components-breadcrumbs.mjs.map +1 -1
  7. package/fesm2022/koobiq-components-code-block.mjs +10 -7
  8. package/fesm2022/koobiq-components-code-block.mjs.map +1 -1
  9. package/fesm2022/koobiq-components-core.mjs +31 -13
  10. package/fesm2022/koobiq-components-core.mjs.map +1 -1
  11. package/fesm2022/koobiq-components-datepicker.mjs +1 -1
  12. package/fesm2022/koobiq-components-datepicker.mjs.map +1 -1
  13. package/fesm2022/koobiq-components-file-upload.mjs +4 -4
  14. package/fesm2022/koobiq-components-file-upload.mjs.map +1 -1
  15. package/fesm2022/koobiq-components-filter-bar.mjs +3 -3
  16. package/fesm2022/koobiq-components-filter-bar.mjs.map +1 -1
  17. package/fesm2022/koobiq-components-form-field.mjs +4 -4
  18. package/fesm2022/koobiq-components-form-field.mjs.map +1 -1
  19. package/fesm2022/koobiq-components-modal.mjs +8 -6
  20. package/fesm2022/koobiq-components-modal.mjs.map +1 -1
  21. package/fesm2022/koobiq-components-select.mjs +31 -9
  22. package/fesm2022/koobiq-components-select.mjs.map +1 -1
  23. package/fesm2022/koobiq-components-tags.mjs +49 -34
  24. package/fesm2022/koobiq-components-tags.mjs.map +1 -1
  25. package/fesm2022/koobiq-components-timezone.mjs +4 -4
  26. package/fesm2022/koobiq-components-timezone.mjs.map +1 -1
  27. package/fesm2022/koobiq-components-tooltip.mjs +2 -2
  28. package/fesm2022/koobiq-components-tooltip.mjs.map +1 -1
  29. package/fesm2022/koobiq-components-tree-select.mjs +2 -2
  30. package/fesm2022/koobiq-components-tree-select.mjs.map +1 -1
  31. package/fesm2022/koobiq-components-tree.mjs +1 -1
  32. package/fesm2022/koobiq-components-tree.mjs.map +1 -1
  33. package/form-field/_form-field-theme.scss +10 -2
  34. package/form-field/form-field-tokens.scss +1 -0
  35. package/form-field/form-field.scss +19 -0
  36. package/package.json +4 -4
  37. package/schematics/ng-add/index.js +2 -2
  38. package/select/select-tokens.scss +2 -2
  39. package/select/select.component.d.ts +10 -1
  40. package/tags/tag-list.component.d.ts +15 -11
  41. package/tags/tag.component.d.ts +5 -2
  42. package/timezone/timezone-option-tokens.scss +0 -10
  43. package/tooltip/_tooltip-theme.scss +5 -1
@@ -16,7 +16,7 @@ import { DELETE, SPACE, BACKSPACE, ENTER, ESCAPE, F2, LEFT_ARROW, TAB, HOME, END
16
16
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17
17
  import { FocusKeyManager } from '@koobiq/cdk/a11y';
18
18
  import { Subject, merge } from 'rxjs';
19
- import { startWith, filter } from 'rxjs/operators';
19
+ import { startWith, takeUntil, filter } from 'rxjs/operators';
20
20
  import { KbqIcon } from '@koobiq/components/icon';
21
21
  import { PlatformModule } from '@angular/cdk/platform';
22
22
 
@@ -253,9 +253,12 @@ class KbqTag extends KbqColorDirective {
253
253
  this.editing = signal(false);
254
254
  /** Emitted when the tag is selected or deselected. */
255
255
  this.selectionChange = new EventEmitter();
256
- /** Emitted when the tag is destroyed. */
256
+ /** Emitted when the tag is destroyed and leaving the DOM. */
257
257
  this.destroyed = new EventEmitter();
258
- /** Emitted when a tag is to be removed. */
258
+ /**
259
+ * Emitted when a tag is to be removed.
260
+ * Fires on programmatic and UI removal (click or keyboard).
261
+ */
259
262
  this.removed = new EventEmitter();
260
263
  this._selected = false;
261
264
  this._selectable = false;
@@ -696,13 +699,21 @@ class KbqTagList {
696
699
  return merge(...this.tags.map((tag) => tag.onBlur));
697
700
  }
698
701
  /**
699
- * Combined stream of all of the child tags' remove change events.
702
+ * Combined stream of all of the child tags' destroyed events.
700
703
  *
701
704
  * @docs-private
702
705
  */
703
706
  get tagRemoveChanges() {
704
707
  return merge(...this.tags.map((tag) => tag.destroyed));
705
708
  }
709
+ /**
710
+ * Combined stream of all of the child tags' removal request events.
711
+ *
712
+ * @docs-private
713
+ */
714
+ get tagBeforeRemoveChanges() {
715
+ return merge(...this.tags.map((tag) => tag.removed));
716
+ }
706
717
  /**
707
718
  * Combined stream of all of the child tags' edit change events.
708
719
  *
@@ -872,7 +883,8 @@ class KbqTagList {
872
883
  this.editable = false;
873
884
  /**
874
885
  * Whether to emit change events when tags are added/removed.
875
- * Set to `false` to prevent the form control from being marked as dirty during programmatic updates.
886
+ *
887
+ * @deprecated No longer needed. Will be removed in the next major release.
876
888
  */
877
889
  this.emitOnTagChanges = true;
878
890
  this._removable = true;
@@ -912,12 +924,16 @@ class KbqTagList {
912
924
  * @docs-private
913
925
  */
914
926
  this.errorState = false;
927
+ /** True when the next `tags.changes` emission is triggered by a UI action, not programmatic update. */
928
+ this.pendingUIChange = false;
915
929
  /**
916
930
  * When a tag is destroyed, we store the index of the destroyed tag until the tags
917
931
  * query list notifies about the update. This is necessary because we cannot determine an
918
932
  * appropriate tag that should receive focus until the array of tags updated completely.
919
933
  */
920
934
  this.lastDestroyedTagIndex = null;
935
+ /** Triggers unsubscription from all per-tags streams when tags are reset. */
936
+ this.tagsSubscriptions$ = new Subject();
921
937
  /** @docs-private */
922
938
  this.onTouched = () => { };
923
939
  /** @docs-private */
@@ -967,8 +983,8 @@ class KbqTagList {
967
983
  // has changed after it was checked" errors from Angular.
968
984
  Promise.resolve().then(() => {
969
985
  this.stateChanges.next();
970
- // do not call on initial
971
- if (currentTags && this.emitOnTagChanges) {
986
+ if (currentTags && this.pendingUIChange) {
987
+ this.pendingUIChange = false;
972
988
  this.propagateTagsChanges();
973
989
  }
974
990
  });
@@ -980,7 +996,7 @@ class KbqTagList {
980
996
  ngOnDestroy() {
981
997
  this.stateChanges.complete();
982
998
  this.focusMonitor.stopMonitoring(this.elementRef);
983
- this.dropSubscriptions();
999
+ this.tagsSubscriptions$.next();
984
1000
  }
985
1001
  /** @docs-private */
986
1002
  updateErrorState() {
@@ -994,6 +1010,10 @@ class KbqTagList {
994
1010
  this.stateChanges.next();
995
1011
  }
996
1012
  }
1013
+ /** Notifies that the next `tags.changes` emission is UI-initiated. */
1014
+ notifyPendingTagChange() {
1015
+ this.pendingUIChange = true;
1016
+ }
997
1017
  /**
998
1018
  * Associates an HTML input element with this tag list.
999
1019
  *
@@ -1223,32 +1243,14 @@ class KbqTagList {
1223
1243
  this.changeDetectorRef.markForCheck();
1224
1244
  }
1225
1245
  resetTags() {
1226
- this.dropSubscriptions();
1246
+ this.tagsSubscriptions$.next();
1227
1247
  this.listenToTagsFocus();
1228
1248
  this.listenToTagsRemoved();
1229
1249
  this.listenToTagsEdit();
1230
1250
  }
1231
- dropSubscriptions() {
1232
- if (this.tagFocusSubscription) {
1233
- this.tagFocusSubscription.unsubscribe();
1234
- this.tagFocusSubscription = null;
1235
- }
1236
- if (this.tagBlurSubscription) {
1237
- this.tagBlurSubscription.unsubscribe();
1238
- this.tagBlurSubscription = null;
1239
- }
1240
- if (this.tagRemoveSubscription) {
1241
- this.tagRemoveSubscription.unsubscribe();
1242
- this.tagRemoveSubscription = null;
1243
- }
1244
- if (this.tagEditSubscription) {
1245
- this.tagEditSubscription.unsubscribe();
1246
- this.tagEditSubscription = null;
1247
- }
1248
- }
1249
1251
  /** Listens to user-generated selection events on each tag. */
1250
1252
  listenToTagsFocus() {
1251
- this.tagFocusSubscription = this.tagFocusChanges.subscribe(({ tag, origin }) => {
1253
+ this.tagFocusChanges.pipe(takeUntil(this.tagsSubscriptions$)).subscribe(({ tag, origin }) => {
1252
1254
  const tagIndex = this.tags.toArray().indexOf(tag);
1253
1255
  if (this.isValidIndex(tagIndex)) {
1254
1256
  this.keyManager.setFocusOrigin(origin);
@@ -1256,13 +1258,13 @@ class KbqTagList {
1256
1258
  }
1257
1259
  this.stateChanges.next();
1258
1260
  });
1259
- this.tagBlurSubscription = this.tagBlurChanges.subscribe(() => {
1261
+ this.tagBlurChanges.pipe(takeUntil(this.tagsSubscriptions$)).subscribe(() => {
1260
1262
  this.blur();
1261
1263
  this.stateChanges.next();
1262
1264
  });
1263
1265
  }
1264
1266
  listenToTagsRemoved() {
1265
- this.tagRemoveSubscription = this.tagRemoveChanges.subscribe((event) => {
1267
+ this.tagRemoveChanges.pipe(takeUntil(this.tagsSubscriptions$)).subscribe((event) => {
1266
1268
  const tag = event.tag;
1267
1269
  const tagIndex = this.tags.toArray().indexOf(event.tag);
1268
1270
  // In case the tag that will be removed is currently focused, we temporarily store
@@ -1272,10 +1274,14 @@ class KbqTagList {
1272
1274
  this.lastDestroyedTagIndex = tagIndex;
1273
1275
  }
1274
1276
  });
1277
+ // Set the pending flag so the subsequent `tags.changes` is treated as UI action.
1278
+ this.tagBeforeRemoveChanges
1279
+ .pipe(takeUntil(this.tagsSubscriptions$))
1280
+ .subscribe(() => (this.pendingUIChange = true));
1275
1281
  }
1276
1282
  listenToTagsEdit() {
1277
- this.tagEditSubscription = this.tagEditChanges
1278
- .pipe(filter(({ type }) => type === 'submit'))
1283
+ this.tagEditChanges
1284
+ .pipe(filter(({ type }) => type === 'submit'), takeUntil(this.tagsSubscriptions$))
1279
1285
  .subscribe(() => this.propagateTagsChanges());
1280
1286
  }
1281
1287
  /** Checks whether any of the tags is focused. */
@@ -1300,6 +1306,7 @@ class KbqTagList {
1300
1306
  this.dropList.dropped
1301
1307
  .pipe(takeUntilDestroyed(this.destroyRef))
1302
1308
  .subscribe(({ currentIndex, previousIndex, event, item }) => {
1309
+ this.pendingUIChange = true;
1303
1310
  const { tag } = item.data;
1304
1311
  this.dropped.emit({ currentIndex, previousIndex, event, tag });
1305
1312
  });
@@ -1509,12 +1516,16 @@ class KbqTagInput {
1509
1516
  }
1510
1517
  /** @docs-private */
1511
1518
  onKeydown(event) {
1519
+ const isSeparatorKey = this.isSeparatorKey(event);
1512
1520
  if (!this.inputElement.value) {
1521
+ if (isSeparatorKey) {
1522
+ event.preventDefault();
1523
+ }
1513
1524
  this._tagList.keydown(event);
1514
1525
  event.stopPropagation();
1515
1526
  return;
1516
1527
  }
1517
- if (this.isSeparatorKey(event) && this.inputElement.value) {
1528
+ if (isSeparatorKey) {
1518
1529
  this.emitTagEnd();
1519
1530
  event.preventDefault();
1520
1531
  }
@@ -1543,6 +1554,7 @@ class KbqTagInput {
1543
1554
  if (!this.hasControl() || (this.hasControl() && !this.ngControl.invalid)) {
1544
1555
  if (this.distinct && this.hasDuplicates)
1545
1556
  return;
1557
+ this._tagList?.notifyPendingTagChange();
1546
1558
  this.tagEnd.emit({ input: this.inputElement, value: this.trimValue(this.inputElement.value) });
1547
1559
  }
1548
1560
  }
@@ -1576,6 +1588,9 @@ class KbqTagInput {
1576
1588
  const tagValues = this._tagList.tags.map(({ value }) => value);
1577
1589
  items = items.filter((item) => !tagValues.includes(item));
1578
1590
  }
1591
+ if (items.length) {
1592
+ this._tagList?.notifyPendingTagChange();
1593
+ }
1579
1594
  items.forEach((item) => this.tagEnd.emit({ input: this.inputElement, value: item }));
1580
1595
  $event.preventDefault();
1581
1596
  $event.stopPropagation();
@@ -1616,7 +1631,7 @@ class KbqTagInput {
1616
1631
  }
1617
1632
  /** Checks whether a keycode is one of the configured separators. */
1618
1633
  isSeparatorKey(event) {
1619
- return this.separators.some((separator) => separator.key === event.key && !event.shiftKey);
1634
+ return this.separators.some((separator) => separator.key === event.key && !hasModifierKey(event));
1620
1635
  }
1621
1636
  /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: KbqTagInput, deps: [{ token: i0.ElementRef }, { token: KBQ_TAGS_DEFAULT_OPTIONS }, { token: i1$1.KbqTrim, optional: true, self: true }, { token: i3.NgControl, optional: true, self: true }, { token: i3$1.KbqAutocompleteTrigger, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Directive }); }
1622
1637
  /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "19.2.20", type: KbqTagInput, isStandalone: true, selector: "input[kbqTagInputFor]", inputs: { separatorKeyCodes: ["kbqTagInputSeparatorKeyCodes", "separatorKeyCodes"], distinct: "distinct", placeholder: "placeholder", id: "id", tagList: ["kbqTagInputFor", "tagList"], addOnBlur: ["kbqTagInputAddOnBlur", "addOnBlur"], addOnPaste: ["kbqTagInputAddOnPaste", "addOnPaste", booleanAttribute], disabled: "disabled" }, outputs: { tagEnd: "kbqTagInputTokenEnd" }, host: { listeners: { "keydown": "onKeydown($event)", "blur": "blur($event)", "focus": "onFocus()", "input": "onInput()", "paste": "onPaste($event)" }, properties: { "id": "id", "attr.disabled": "disabled || null", "attr.placeholder": "placeholder || null" }, classAttribute: "kbq-tag-input" }, exportAs: ["kbqTagInput", "kbqTagInputFor"], usesOnChanges: true, hostDirectives: [{ directive: i1.KbqFieldSizingContent }], ngImport: i0 }); }