noph-ui 0.8.11 → 0.8.13

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.
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import Menu from '../menu/Menu.svelte'
3
3
  import { isFirstInvalidControlInForm } from '../text-field/report-validity.js'
4
- import { generateUUIDv4, isIOS } from '../utils.js'
4
+ import { generateUUIDv4 } from '../utils.js'
5
5
  import type { SelectProps } from './types.ts'
6
6
  import Item from '../list/Item.svelte'
7
7
 
@@ -11,6 +11,7 @@
11
11
  error = false,
12
12
  errorText = '',
13
13
  supportingText = '',
14
+ tabindex = 0,
14
15
  start,
15
16
  label,
16
17
  style,
@@ -26,6 +27,7 @@
26
27
  })
27
28
  let selectElement: HTMLSelectElement | undefined = $state()
28
29
  let menuElement: HTMLDivElement | undefined = $state()
30
+ let field: HTMLDivElement | undefined = $state()
29
31
  let menuId = $state(`--select-${generateUUIDv4()}`)
30
32
  let menuOpen = $state(false)
31
33
  let selectedLabel = $derived.by<string>(() => {
@@ -42,7 +44,6 @@
42
44
  if (selectElement) {
43
45
  selectElement.form?.addEventListener('reset', () => {
44
46
  error = false
45
- value = ''
46
47
  })
47
48
  selectElement.addEventListener('invalid', (event) => {
48
49
  event.preventDefault()
@@ -78,8 +79,7 @@
78
79
  </svg>
79
80
  {/snippet}
80
81
 
81
- <!-- svelte-ignore a11y_no_noninteractive_element_to_interactive_role -->
82
- <label
82
+ <div
83
83
  style={(variant === 'outlined'
84
84
  ? '--top-space:1rem;--bottom-space:1rem;--floating-label-top:-0.5rem;--floating-label-left:-2.25rem;--_focus-outline-width:3px;'
85
85
  : !label?.length
@@ -88,28 +88,6 @@
88
88
  class={['text-field', attributes.class]}
89
89
  bind:this={element}
90
90
  bind:clientWidth
91
- role="combobox"
92
- aria-controls="listbox"
93
- aria-expanded={menuOpen}
94
- onclick={(event) => {
95
- event.preventDefault()
96
- menuElement?.showPopover()
97
- menuElement?.focus()
98
- }}
99
- onkeydown={(event) => {
100
- if (event.key === 'Tab') {
101
- if (isIOS()) {
102
- event.preventDefault()
103
- }
104
- menuElement?.hidePopover()
105
- } else {
106
- event.preventDefault()
107
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
108
- menuElement?.showPopover()
109
- ;(menuElement?.firstElementChild as HTMLElement)?.focus()
110
- }
111
- }
112
- }}
113
91
  >
114
92
  <div
115
93
  class="field"
@@ -120,6 +98,27 @@
120
98
  class:with-end={true}
121
99
  class:disabled={attributes.disabled}
122
100
  class:outlined={variant === 'outlined'}
101
+ role="combobox"
102
+ tabindex={attributes.disabled ? -1 : tabindex}
103
+ aria-controls="listbox"
104
+ aria-expanded={menuOpen}
105
+ aria-label={label}
106
+ bind:this={field}
107
+ onclick={(event) => {
108
+ event.preventDefault()
109
+ menuElement?.showPopover()
110
+ }}
111
+ onkeydown={(event) => {
112
+ if (event.key === 'Tab') {
113
+ menuElement?.hidePopover()
114
+ } else {
115
+ event.preventDefault()
116
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
117
+ menuElement?.showPopover()
118
+ ;(menuElement?.firstElementChild as HTMLElement)?.focus()
119
+ }
120
+ }
121
+ }}
123
122
  >
124
123
  <div class="container-overflow">
125
124
  {#if variant === 'filled'}
@@ -158,7 +157,13 @@
158
157
  </div>
159
158
  {/if}
160
159
  <div class="content">
161
- <select aria-label={label} {...attributes} bind:value bind:this={selectElement}>
160
+ <select
161
+ tabindex="-1"
162
+ aria-label={label}
163
+ {...attributes}
164
+ bind:value
165
+ bind:this={selectElement}
166
+ >
162
167
  {#each options as option}
163
168
  <option value={option.value} selected={option.selected}>{option.label}</option>
164
169
  {/each}
@@ -187,7 +192,7 @@
187
192
  </div>
188
193
  {/if}
189
194
  </div>
190
- </label>
195
+ </div>
191
196
 
192
197
  <Menu
193
198
  style="position-anchor:{menuId};min-width: {clientWidth}px;"
@@ -208,9 +213,7 @@
208
213
  onclick={(event) => {
209
214
  value = option.value
210
215
  menuElement?.hidePopover()
211
- if (!isIOS()) {
212
- element?.focus()
213
- }
216
+ field?.focus()
214
217
  event.preventDefault()
215
218
  }}
216
219
  onkeydown={(event) => {
@@ -227,6 +230,9 @@
227
230
  menuElement?.hidePopover()
228
231
  event.preventDefault()
229
232
  }
233
+ if (event.key === 'Tab') {
234
+ menuElement?.hidePopover()
235
+ }
230
236
  }}
231
237
  variant="button"
232
238
  selected={value === option.value}>{option.label}</Item
@@ -243,7 +249,7 @@
243
249
  z-index: 1;
244
250
  }
245
251
  .field.menu-open .active-indicator::after,
246
- .field:has(select:focus-visible) .active-indicator::after {
252
+ .field:focus .active-indicator::after {
247
253
  opacity: 1;
248
254
  }
249
255
  .active-indicator::after {
@@ -332,6 +338,7 @@
332
338
  writing-mode: horizontal-tb;
333
339
  max-width: var(--np-select-max-width, 100%);
334
340
  min-width: var(--np-select-min-width, 210px);
341
+ outline: none;
335
342
  }
336
343
 
337
344
  .supporting-text {
@@ -412,13 +419,13 @@
412
419
 
413
420
  .no-label .content,
414
421
  .field.menu-open .content,
415
- .field:has(select:focus-visible) .content,
422
+ .field:focus .content,
416
423
  .field:has(select option:checked:not([value=''])) .content {
417
424
  opacity: 1;
418
425
  }
419
426
 
420
427
  .field:not(.error).menu-open .down,
421
- .field:not(.error):has(select:focus-visible) .down {
428
+ .field:not(.error):focus .down {
422
429
  color: var(--np-color-primary);
423
430
  }
424
431
  .icon .down {
@@ -540,9 +547,8 @@
540
547
  }
541
548
 
542
549
  .with-end.menu-open .label-wrapper,
543
- .with-end:has(select:focus-visible option:checked:not([value=''])) .label-wrapper,
544
- .with-end:has(select option:checked:not([value=''])) .label-wrapper,
545
- .with-end:has(select:focus-visible) .label-wrapper {
550
+ .with-end:focus:has(select option:checked:not([value=''])) .label-wrapper,
551
+ .with-end:focus .label-wrapper {
546
552
  margin-inline-end: 1rem;
547
553
  }
548
554
  .notch {
@@ -557,16 +563,16 @@
557
563
  opacity: 0;
558
564
  }
559
565
 
560
- .field:not(.menu-open):has(select:not(:focus-visible)) .label {
566
+ .field:not(.menu-open):not(:focus) .label {
561
567
  position: absolute;
562
568
  top: 1rem;
563
569
  left: 0rem;
564
570
  }
565
571
 
566
572
  .field.menu-open .label,
567
- .field:has(select:focus-visible option:checked:not([value=''])) .label,
573
+ .field:focus:has(select option:checked:not([value=''])) .label,
568
574
  .field:has(select option:checked:not([value=''])) .label,
569
- .field:has(select:focus-visible) .label {
575
+ .field:focus .label {
570
576
  font-size: 0.75rem;
571
577
  line-height: 1rem;
572
578
  transform-origin: top left;
@@ -597,12 +603,12 @@
597
603
  }
598
604
 
599
605
  .field.menu-open .label,
600
- .field:has(select:focus-visible) .label {
606
+ .field:focus .label {
601
607
  color: var(--np-color-primary);
602
608
  }
603
609
  .error .label,
604
610
  .error.menu-open .label,
605
- .error:has(select:focus-visible) .label {
611
+ .error:focus .label {
606
612
  color: var(--np-color-error);
607
613
  }
608
614
  .disabled .label {
@@ -692,7 +698,7 @@
692
698
  }
693
699
 
694
700
  .field.menu-open .outline-notch::before,
695
- .field:has(select:focus-visible) .outline-notch::before,
701
+ .field:focus .outline-notch::before,
696
702
  .field:has(select option:checked:not([value=''])) .outline-notch::before {
697
703
  border-top-style: none;
698
704
  }
@@ -735,9 +741,9 @@
735
741
  .field.menu-open .outline-start::after,
736
742
  .field.menu-open .outline-end::after,
737
743
  .field.menu-open .outline-notch::after,
738
- .field:has(select:focus-visible) .outline-start::after,
739
- .field:has(select:focus-visible) .outline-end::after,
740
- .field:has(select:focus-visible) .outline-notch::after {
744
+ .field:focus .outline-start::after,
745
+ .field:focus .outline-end::after,
746
+ .field:focus .outline-notch::after {
741
747
  opacity: 1;
742
748
  }
743
749
  .np-outline {
@@ -752,13 +758,13 @@
752
758
  }
753
759
 
754
760
  .field.menu-open .np-outline,
755
- .field:has(select:focus-visible) .np-outline {
761
+ .field:focus .np-outline {
756
762
  border-color: var(--np-color-primary);
757
763
  color: var(--np-color-primary);
758
764
  }
759
765
  .error .np-outline,
760
766
  .error.menu-open .np-outline,
761
- .error:has(select:focus-visible) .np-outline {
767
+ .error:focus .np-outline {
762
768
  border-color: var(--np-color-error);
763
769
  }
764
770
  .disabled .np-outline {
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  declare const generateUUIDv4: () => string;
2
- declare const isIOS: () => boolean;
3
- export { generateUUIDv4, isIOS };
2
+ export { generateUUIDv4 };
package/dist/utils.js CHANGED
@@ -4,7 +4,4 @@ const generateUUIDv4 = () => {
4
4
  return v.toString(16);
5
5
  });
6
6
  };
7
- const isIOS = () => {
8
- return /iPhone|iPad|iPod/i.test(navigator.userAgent);
9
- };
10
- export { generateUUIDv4, isIOS };
7
+ export { generateUUIDv4 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {