noph-ui 0.12.8 → 0.12.10

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.
package/README.md CHANGED
@@ -64,6 +64,7 @@ Beta (No breaking changes expected)
64
64
  - Radio
65
65
  - Ripple
66
66
  - Segmented buttons
67
+ - Select
67
68
  - Snackbar
68
69
  - Text fields
69
70
 
@@ -75,7 +76,6 @@ In progress (Breaking changes expected)
75
76
  - Navigation Drawer (Docs missing)
76
77
  - Navigation Rail (Badge is missing + Docs missing)
77
78
  - Tooltips (Positioning missing)
78
- - Select (Docs missing)
79
79
 
80
80
  Pending
81
81
 
@@ -15,6 +15,7 @@
15
15
  disabled = false,
16
16
  loading = false,
17
17
  keepTooltipOnClick,
18
+ loadingAriaLabel,
18
19
  ...attributes
19
20
  }: ButtonProps = $props()
20
21
 
@@ -34,7 +35,7 @@
34
35
  {/if}
35
36
  {#if loading}
36
37
  <div class="circular-progress">
37
- <CircularProgress indeterminate />
38
+ <CircularProgress aria-label={loadingAriaLabel} indeterminate />
38
39
  </div>
39
40
  {/if}
40
41
  <div class="button-icon">
@@ -7,6 +7,7 @@ interface ButtonButtonProps extends HTMLButtonAttributes {
7
7
  element?: HTMLElement;
8
8
  disabled?: boolean;
9
9
  loading?: boolean;
10
+ loadingAriaLabel?: string;
10
11
  keepTooltipOnClick?: boolean;
11
12
  }
12
13
  interface ButtonAnchorProps extends HTMLAnchorAttributes {
@@ -17,6 +18,7 @@ interface ButtonAnchorProps extends HTMLAnchorAttributes {
17
18
  keepTooltipOnClick?: boolean;
18
19
  disabled?: boolean;
19
20
  loading?: boolean;
21
+ loadingAriaLabel?: string;
20
22
  }
21
23
  interface IconButtonButtonProps extends HTMLButtonAttributes {
22
24
  variant?: 'text' | 'filled' | 'outlined' | 'tonal';
@@ -13,6 +13,7 @@
13
13
  let clientWidth = $state(0)
14
14
  let clientHeight = $state(0)
15
15
  let innerHeight = $state(0)
16
+ let menuOpen = $state(false)
16
17
 
17
18
  showPopover = () => {
18
19
  element?.showPopover()
@@ -23,7 +24,7 @@
23
24
  }
24
25
 
25
26
  const refreshValues = () => {
26
- if (element && anchor) {
27
+ if (element && anchor && menuOpen) {
27
28
  const anchorRect = anchor.getBoundingClientRect()
28
29
  let maxHeight: number
29
30
  if (innerHeight - anchorRect.bottom > anchorRect.top) {
@@ -33,9 +34,9 @@
33
34
  }
34
35
  element.style.maxHeight =
35
36
  maxHeight > innerHeight - anchorRect.height
36
- ? `${innerHeight - anchorRect.height - 20}px`
37
- : `${maxHeight - 20}px`
38
- if (!('anchorName' in document.documentElement.style)) {
37
+ ? `${innerHeight - anchorRect.height - 4}px`
38
+ : `${maxHeight - 4}px`
39
+ if (!('positionArea' in document.documentElement.style)) {
39
40
  const docClientWidth = document.documentElement.clientWidth
40
41
  if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
41
42
  element.style.top = `${anchorRect.top - clientHeight - 2}px`
@@ -86,9 +87,6 @@
86
87
  },
87
88
  { passive: true },
88
89
  )
89
- anchor.addEventListener('click', () => {
90
- refreshValues()
91
- })
92
90
  if (
93
91
  'anchorName' in document.documentElement.style &&
94
92
  !anchor.style.getPropertyValue('anchor-name')
@@ -103,41 +101,54 @@
103
101
 
104
102
  <svelte:window bind:innerHeight onresize={refreshValues} />
105
103
  <div
104
+ role="menu"
106
105
  {...attributes}
107
106
  bind:this={element}
108
107
  bind:clientWidth
109
108
  bind:clientHeight
109
+ ontoggle={(event) => {
110
+ let { newState } = event
111
+ menuOpen = newState === 'open'
112
+ attributes.ontoggle?.(event)
113
+ }}
110
114
  popover="auto"
111
- class={['np-menu', attributes.class]}
112
- role="menu"
115
+ class={['np-menu-container', attributes.class]}
113
116
  >
114
- {@render children()}
117
+ <div class="np-menu">
118
+ {@render children()}
119
+ </div>
115
120
  </div>
116
121
 
117
122
  <style>
118
- .np-menu[popover] {
123
+ .np-menu {
124
+ overflow-y: auto;
125
+ overflow-x: hidden;
126
+ flex: 1;
127
+ padding: 0.5rem 0;
128
+ scrollbar-color: var(--np-color-on-surface-variant) transparent;
129
+ scrollbar-width: thin;
130
+ }
131
+ .np-menu-container[popover] {
119
132
  color: var(--np-menu-text-color, var(--np-color-on-surface));
120
133
  background-color: var(--np-menu-container-color, var(--np-color-surface-container));
121
- overflow-y: auto;
122
134
  border: none;
123
135
  border-radius: var(--np-menu-container-shape, var(--np-shape-corner-extra-small));
124
- padding: 0.5rem 0;
136
+ padding: 0;
125
137
  box-shadow: var(--np-elevation-2);
126
138
  margin: var(--np-menu-margin, 2px);
127
139
  inset: auto;
128
- scrollbar-color: var(--np-color-on-surface-variant) transparent;
129
- scrollbar-width: thin;
130
140
  transition:
131
141
  display 0.2s allow-discrete,
132
142
  opacity 0.2s linear;
133
143
  opacity: 0;
134
144
  justify-self: var(--np-menu-justify-self, anchor-center);
135
145
  position-area: var(--np-menu-position-area, bottom center);
136
- position-try: most-height flip-block;
146
+ position-try: normal flip-block;
137
147
  }
138
148
 
139
- .np-menu:popover-open {
149
+ .np-menu-container:popover-open {
140
150
  opacity: 1;
151
+ display: flex;
141
152
  @starting-style {
142
153
  opacity: 0;
143
154
  }
@@ -33,6 +33,7 @@
33
33
  let errorRaw = $state(error)
34
34
  let selectElement: HTMLSelectElement | undefined = $state()
35
35
  let menuElement: HTMLDivElement | undefined = $state()
36
+ let anchorElement: HTMLDivElement | undefined = $state()
36
37
  let field: HTMLDivElement | undefined = $state()
37
38
  let clientWidth = $state(0)
38
39
  let menuId = $state(`--select-${crypto.randomUUID()}`)
@@ -87,6 +88,7 @@
87
88
  aria-controls="listbox"
88
89
  aria-expanded={menuOpen}
89
90
  aria-label={attributes['aria-label'] || label}
91
+ aria-disabled={disabled}
90
92
  data-testid={attributes['data-testid']}
91
93
  bind:this={field}
92
94
  bind:clientWidth
@@ -102,7 +104,7 @@
102
104
  event.preventDefault()
103
105
  if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
104
106
  menuElement?.showPopover()
105
- ;(menuElement?.firstElementChild as HTMLElement)?.focus()
107
+ ;(menuElement?.firstElementChild?.firstElementChild as HTMLElement)?.focus()
106
108
  }
107
109
  }
108
110
  }}
@@ -129,7 +131,7 @@
129
131
  <div class="outline-end"></div>
130
132
  </div>
131
133
  {/if}
132
- <div class="np-container" style="anchor-name:{menuId};">
134
+ <div class="np-container" bind:this={anchorElement} style="anchor-name:{menuId};">
133
135
  {#if start}
134
136
  <div class="start">
135
137
  <span class="icon leading">{@render start()}</span>
@@ -175,8 +177,6 @@
175
177
  <div class="input">
176
178
  {#if selectedLabel}
177
179
  {selectedLabel}
178
- {:else}
179
- &nbsp;
180
180
  {/if}
181
181
  </div>
182
182
  </div>
@@ -201,14 +201,14 @@
201
201
  <Menu
202
202
  style="position-anchor:{menuId};min-width:{clientWidth}px;"
203
203
  popover="manual"
204
+ role="listbox"
204
205
  --np-menu-justify-self="none"
205
- --np-menu-position-area-fallback="top span-right"
206
206
  --np-menu-position-area="bottom span-right"
207
207
  --np-menu-margin="2px 0"
208
208
  --np-menu-container-shape={variant === 'outlined'
209
209
  ? 'var(--np-outlined-select-text-field-container-shape)'
210
210
  : 'var(--np-filled-select-text-field-container-shape)'}
211
- anchor={element}
211
+ anchor={anchorElement}
212
212
  ontoggle={({ newState }) => {
213
213
  if (newState === 'open') {
214
214
  menuOpen = true
@@ -322,6 +322,7 @@
322
322
  min-height: 100%;
323
323
  min-width: min-content;
324
324
  position: relative;
325
+ user-select: none;
325
326
  }
326
327
  .outlined .container-overflow {
327
328
  border-start-start-radius: var(
@@ -432,6 +433,7 @@
432
433
  overflow-x: hidden;
433
434
  text-align: inherit;
434
435
  width: 100%;
436
+ height: 1.5rem;
435
437
 
436
438
  &::placeholder {
437
439
  color: currentColor;
@@ -600,6 +602,7 @@
600
602
  }
601
603
 
602
604
  .with-start.menu-open .label,
605
+ .with-start:focus .label,
603
606
  .with-start:has(select:focus-visible option:checked:not([value=''])) .label,
604
607
  .with-start:has(select option:checked:not([value=''])) .label,
605
608
  .with-start:has(select:focus-visible) .label {
@@ -7,7 +7,6 @@ export interface SelectProps extends Omit<HTMLSelectAttributes, 'multiple' | 'si
7
7
  errorText?: string;
8
8
  variant?: 'outlined' | 'filled';
9
9
  start?: Snippet;
10
- end?: Snippet;
11
10
  noAsterisk?: boolean;
12
11
  element?: HTMLSpanElement;
13
12
  options: {
@@ -14,7 +14,7 @@
14
14
  let anchor: HTMLElement | undefined = $state()
15
15
 
16
16
  const refreshValues = () => {
17
- if (element && anchor && !('anchorName' in document.documentElement.style)) {
17
+ if (element && anchor && !('positionArea' in document.documentElement.style)) {
18
18
  const docClientWidth = document.documentElement.clientWidth
19
19
  const anchorRect = anchor.getBoundingClientRect()
20
20
  if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {