@ship-ui/core 0.13.2 → 0.13.3

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.
@@ -3027,6 +3027,7 @@ class ShipSelectComponent {
3027
3027
  this.options = model([]);
3028
3028
  this.selectedOptions = model([]);
3029
3029
  this.cleared = output();
3030
+ this.#previousSelectedOptions = signal(null);
3030
3031
  this.inlineTemplate = contentChild(TemplateRef);
3031
3032
  this.optionsWrapRef = viewChild.required('optionsWrap');
3032
3033
  this.inputRefInput = signal(null);
@@ -3094,6 +3095,7 @@ class ShipSelectComponent {
3094
3095
  scoredOptions.sort((a, b) => b.score - a.score);
3095
3096
  return scoredOptions.map((scoredOption) => scoredOption.item);
3096
3097
  });
3098
+ this.#componentId = generateUniqueId();
3097
3099
  this.inputRefEl = computed(() => {
3098
3100
  const inputRefInput = this.inputRefInput();
3099
3101
  if (inputRefInput === null)
@@ -3104,6 +3106,10 @@ class ShipSelectComponent {
3104
3106
  return null;
3105
3107
  }
3106
3108
  input.autocomplete = 'off';
3109
+ input.setAttribute('role', 'combobox');
3110
+ input.setAttribute('id', `combobox-${this.#componentId}`);
3111
+ input.setAttribute('aria-haspopup', 'listbox');
3112
+ input.setAttribute('aria-owns', 'optionsWrapId');
3107
3113
  this.#createCustomInputEventListener(input);
3108
3114
  input.addEventListener('focus', () => {
3109
3115
  if (this.readonly())
@@ -3131,11 +3137,23 @@ class ShipSelectComponent {
3131
3137
  return;
3132
3138
  }
3133
3139
  this.setSelectedOptionsFromValue(newInputValue);
3134
- this.setInputValueFromSelectedOptions();
3140
+ this.setInputValueFromOptions(this.selectedOptions());
3135
3141
  this.#setFirstSelectedOptionAsFocused();
3136
3142
  });
3137
3143
  return input;
3138
3144
  });
3145
+ this.focusEffect = effect(() => {
3146
+ const input = this.inputRefEl();
3147
+ if (!input)
3148
+ return;
3149
+ const focusedId = this.getLabelAsSlug(this.filteredOptions()[this.focusedOptionIndex()]);
3150
+ if (focusedId) {
3151
+ input.setAttribute('aria-activedescendant', focusedId);
3152
+ }
3153
+ else {
3154
+ input.removeAttribute('aria-activedescendant');
3155
+ }
3156
+ });
3139
3157
  this.openAbortController = null;
3140
3158
  this.isOpenEffect = effect(() => {
3141
3159
  const isOpen = this.isOpen();
@@ -3146,6 +3164,7 @@ class ShipSelectComponent {
3146
3164
  const input = this.inputRefEl();
3147
3165
  if (!input)
3148
3166
  return;
3167
+ input.setAttribute('aria-expanded', this.isOpen().toString());
3149
3168
  input.addEventListener('keydown', (e) => {
3150
3169
  if (e.key === 'Escape' || e.key === 'Tab') {
3151
3170
  e.preventDefault();
@@ -3173,6 +3192,7 @@ class ShipSelectComponent {
3173
3192
  const input = this.inputRefEl();
3174
3193
  if (!input)
3175
3194
  return;
3195
+ input.setAttribute('aria-expanded', this.isOpen().toString());
3176
3196
  if (this.openAbortController) {
3177
3197
  this.openAbortController.abort();
3178
3198
  this.openAbortController = null;
@@ -3192,7 +3212,7 @@ class ShipSelectComponent {
3192
3212
  return;
3193
3213
  this.disabled.set(input.disabled);
3194
3214
  this.setSelectedOptionsFromValue(input.value);
3195
- this.setInputValueFromSelectedOptions();
3215
+ this.setInputValueFromOptions(this.selectedOptions());
3196
3216
  });
3197
3217
  this.selectedLabels = computed(() => {
3198
3218
  const selected = this.selectedOptions();
@@ -3237,6 +3257,7 @@ class ShipSelectComponent {
3237
3257
  });
3238
3258
  }
3239
3259
  #selfRef;
3260
+ #previousSelectedOptions;
3240
3261
  #inputObserver;
3241
3262
  #calculateMatchScore(option, input) {
3242
3263
  if (!input)
@@ -3271,6 +3292,7 @@ class ShipSelectComponent {
3271
3292
  score += matchCount * 20;
3272
3293
  return score;
3273
3294
  }
3295
+ #componentId;
3274
3296
  #selectedOptionsEffect;
3275
3297
  ngOnInit() {
3276
3298
  this.setInitInput();
@@ -3311,15 +3333,14 @@ class ShipSelectComponent {
3311
3333
  });
3312
3334
  this.selectedOptions.set(selectMultiple ? selectedOptions : [selectedOptions[0]]);
3313
3335
  }
3314
- setInputValueFromSelectedOptions() {
3315
- const selectedOptions = this.selectedOptions();
3336
+ setInputValueFromOptions(options) {
3316
3337
  const valueKey = this.value();
3317
- if (selectedOptions.length === 0) {
3338
+ if (options.length === 0) {
3318
3339
  this.inputValue.set('');
3319
3340
  this.updateInputElValue();
3320
3341
  return;
3321
3342
  }
3322
- const inputValue = selectedOptions
3343
+ const inputValue = options
3323
3344
  .map((option) => {
3324
3345
  const optionValue = valueKey ? this.#getProperty(option, valueKey) : option;
3325
3346
  return optionValue;
@@ -3334,6 +3355,12 @@ class ShipSelectComponent {
3334
3355
  return option;
3335
3356
  return this.#getProperty(option, label);
3336
3357
  }
3358
+ getLabelAsSlug(option) {
3359
+ const label = this.getLabel(option);
3360
+ if (!label || typeof label !== 'string')
3361
+ return '';
3362
+ return label.replaceAll(' ', '-');
3363
+ }
3337
3364
  toggleOptionByIndex(optionIndex, event) {
3338
3365
  const option = this.filteredOptions()[optionIndex];
3339
3366
  if ((this.asFreeText() && optionIndex === -1) || !option) {
@@ -3349,6 +3376,7 @@ class ShipSelectComponent {
3349
3376
  const selectedOptionValues = this.selectedOptionValues();
3350
3377
  const optionValue = valueKey ? this.#getProperty(option, valueKey) : option;
3351
3378
  this.prevInputValue.set(null);
3379
+ this.#previousSelectedOptions.set(null);
3352
3380
  this.selectedOptions.update((selectedOptions) => {
3353
3381
  const index = selectedOptionValues.indexOf(optionValue);
3354
3382
  if (index > -1) {
@@ -3372,7 +3400,7 @@ class ShipSelectComponent {
3372
3400
  else {
3373
3401
  this.isOpen.set(false);
3374
3402
  }
3375
- this.setInputValueFromSelectedOptions();
3403
+ this.setInputValueFromOptions(this.selectedOptions());
3376
3404
  if (selectMultiple && this.hasSearch()) {
3377
3405
  this.inputValue.set('');
3378
3406
  this.updateInputElValue();
@@ -3383,7 +3411,7 @@ class ShipSelectComponent {
3383
3411
  this.selectedOptions.update((selectedOptions) => {
3384
3412
  return [...selectedOptions.slice(0, optionRemoveIndex), ...selectedOptions.slice(optionRemoveIndex + 1)];
3385
3413
  });
3386
- this.setInputValueFromSelectedOptions();
3414
+ this.setInputValueFromOptions(this.selectedOptions());
3387
3415
  }
3388
3416
  isSelected(optionIndex) {
3389
3417
  const valueKey = this.value();
@@ -3400,6 +3428,9 @@ class ShipSelectComponent {
3400
3428
  this.inputValue.set('');
3401
3429
  this.updateInputElValue();
3402
3430
  }
3431
+ else {
3432
+ this.#previousSelectedOptions.set(this.selectedOptions());
3433
+ }
3403
3434
  if (!this.selectMultiple()) {
3404
3435
  this.#setFirstSelectedOptionAsFocused();
3405
3436
  }
@@ -3418,16 +3449,20 @@ class ShipSelectComponent {
3418
3449
  close() {
3419
3450
  this.isOpen.set(false);
3420
3451
  const prevInputValue = this.prevInputValue();
3452
+ const prevSelectedOptions = this.#previousSelectedOptions();
3421
3453
  if (this.asFreeText()) {
3422
3454
  this.updateInputElValue();
3423
3455
  return;
3424
3456
  }
3425
3457
  if (this.hasSearch() && prevInputValue) {
3426
3458
  this.inputValue.set(prevInputValue);
3427
- this.setInputValueFromSelectedOptions();
3459
+ this.setInputValueFromOptions(this.selectedOptions());
3460
+ }
3461
+ if (!this.hasSearch() && prevSelectedOptions !== null) {
3462
+ this.setInputValueFromOptions(prevSelectedOptions);
3428
3463
  }
3429
3464
  if (this.selectMultiple()) {
3430
- this.setInputValueFromSelectedOptions();
3465
+ this.setInputValueFromOptions(this.selectedOptions());
3431
3466
  }
3432
3467
  }
3433
3468
  clear($event) {
@@ -3585,11 +3620,13 @@ class ShipSelectComponent {
3585
3620
  }
3586
3621
  </sh-form-field>
3587
3622
 
3588
- <div class="ship-options" #optionsWrap>
3623
+ <div class="ship-options" #optionsWrap id="optionsWrapId" role="listbox">
3589
3624
  @for (option of filteredOptions(); track $index) {
3590
3625
  <li
3591
3626
  (click)="toggleOptionByIndex($index)"
3592
3627
  class="option"
3628
+ [id]="this.getLabelAsSlug(option)"
3629
+ [attr.aria-selected]="isSelected($index)"
3593
3630
  [class.selected]="isSelected($index)"
3594
3631
  [class.focused]="$index === focusedOptionIndex()">
3595
3632
  @if (selectMultiple()) {
@@ -3711,11 +3748,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImpor
3711
3748
  }
3712
3749
  </sh-form-field>
3713
3750
 
3714
- <div class="ship-options" #optionsWrap>
3751
+ <div class="ship-options" #optionsWrap id="optionsWrapId" role="listbox">
3715
3752
  @for (option of filteredOptions(); track $index) {
3716
3753
  <li
3717
3754
  (click)="toggleOptionByIndex($index)"
3718
3755
  class="option"
3756
+ [id]="this.getLabelAsSlug(option)"
3757
+ [attr.aria-selected]="isSelected($index)"
3719
3758
  [class.selected]="isSelected($index)"
3720
3759
  [class.focused]="$index === focusedOptionIndex()">
3721
3760
  @if (selectMultiple()) {