noph-ui 0.15.5 → 0.16.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.
@@ -7,6 +7,7 @@
7
7
  element = $bindable(),
8
8
  showPopover = $bindable(),
9
9
  hidePopover = $bindable(),
10
+ style,
10
11
  ...attributes
11
12
  }: MenuProps = $props()
12
13
 
@@ -57,29 +58,36 @@
57
58
  $effect(refreshValues)
58
59
 
59
60
  const getScrollableParent = (start: HTMLElement) => {
60
- let element: HTMLElement | null = start
61
- while (element) {
62
- const style = getComputedStyle(element)
61
+ let el: HTMLElement | null = start
62
+ while (el) {
63
+ const style = getComputedStyle(el)
63
64
  const overflowY = style.overflowY
64
65
  const overflowX = style.overflowX
65
66
  const isScrollableY =
66
- (overflowY === 'auto' || overflowY === 'scroll') &&
67
- element.scrollHeight > element.clientHeight
67
+ (overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight
68
68
  const isScrollableX =
69
- (overflowX === 'auto' || overflowX === 'scroll') &&
70
- element.scrollWidth > element.clientWidth
69
+ (overflowX === 'auto' || overflowX === 'scroll') && el.scrollWidth > el.clientWidth
71
70
 
72
71
  if (isScrollableY || isScrollableX) {
73
- return element
72
+ return el
74
73
  }
75
74
 
76
- element = element.parentElement
75
+ el = el.parentElement
77
76
  }
78
77
  return window
79
78
  }
80
79
 
81
80
  $effect(() => {
82
81
  if (anchor && element) {
82
+ if (style) {
83
+ const styleEntries = style
84
+ .split(';')
85
+ .filter(Boolean)
86
+ .map((entry) => entry.split(':').map((str) => str.trim()))
87
+ styleEntries.forEach(([key, value]) => {
88
+ element?.style.setProperty(key, value)
89
+ })
90
+ }
83
91
  getScrollableParent(element).addEventListener(
84
92
  'scroll',
85
93
  () => {
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ let { checked = $bindable(), disabled = false }: { checked: boolean; disabled?: boolean } =
3
+ $props()
4
+ </script>
5
+
6
+ <div class={['np-host']}>
7
+ <div
8
+ class={['np-container', checked ? 'checked' : 'unchecked', disabled ? 'disabled' : 'enabled']}
9
+ >
10
+ <div class="np-outline"></div>
11
+ <div class="np-background"></div>
12
+ <svg class="np-icon" viewBox="0 0 18 18" aria-hidden="true">
13
+ <rect class="mark short" />
14
+ <rect class="mark long" />
15
+ </svg>
16
+ </div>
17
+ </div>
18
+
19
+ <style>
20
+ .np-host {
21
+ border-start-start-radius: var(--np-checkbox-container-shape, 2px);
22
+ border-start-end-radius: var(--np-checkbox-container-shape, 2px);
23
+ border-end-end-radius: var(--np-checkbox-container-shape, 2px);
24
+ border-end-start-radius: var(--np-checkbox-container-shape, 2px);
25
+ display: inline-flex;
26
+ height: 18px;
27
+ position: relative;
28
+ vertical-align: top;
29
+ width: 18px;
30
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
31
+ cursor: pointer;
32
+ }
33
+ .np-container {
34
+ border-radius: inherit;
35
+ display: flex;
36
+ height: 100%;
37
+ place-content: center;
38
+ place-items: center;
39
+ position: relative;
40
+ width: 100%;
41
+ }
42
+ .np-outline,
43
+ .np-background,
44
+ .np-icon {
45
+ inset: 0;
46
+ position: absolute;
47
+ }
48
+ .np-outline,
49
+ .np-background {
50
+ border-radius: inherit;
51
+ }
52
+ .np-outline {
53
+ border-color: var(--np-checkbox-outline-color, var(--np-color-on-surface-variant));
54
+ border-style: solid;
55
+ border-width: 2px;
56
+ box-sizing: border-box;
57
+ }
58
+ :where(:hover) .np-outline {
59
+ border-color: var(--np-color-on-surface);
60
+ border-width: 2px;
61
+ }
62
+ :where(:focus-within) .np-outline {
63
+ border-color: var(--np-color-on-surface);
64
+ border-width: 2px;
65
+ }
66
+
67
+ .np-container.disabled .np-outline {
68
+ border-color: var(--np-color-on-surface);
69
+ border-width: 2px;
70
+ opacity: 0.38;
71
+ }
72
+ .np-container.checked.disabled .np-outline {
73
+ visibility: hidden;
74
+ }
75
+ .np-background {
76
+ background-color: var(--np-checkbox-selected-container-color, var(--np-color-primary));
77
+ }
78
+ .np-background,
79
+ .np-icon {
80
+ opacity: 0;
81
+ transition-duration: 150ms, 50ms;
82
+ transition-property: transform, opacity;
83
+ transition-timing-function: cubic-bezier(0.3, 0, 0.8, 0.15), linear;
84
+ transform: scale(0.6);
85
+ }
86
+ .np-container.checked .np-background,
87
+ .np-container.checked .np-icon {
88
+ opacity: 1;
89
+ transition-duration: 350ms, 50ms;
90
+ transition-timing-function: cubic-bezier(0.05, 0.7, 0.1, 1), linear;
91
+ transform: scale(1);
92
+ }
93
+ .np-container.checked.disabled .np-background {
94
+ background: var(--np-color-on-surface);
95
+ opacity: 0.38;
96
+ }
97
+ .np-icon {
98
+ fill: var(--np-checkbox-selected-icon-color, var(--np-color-on-primary));
99
+ height: 18px;
100
+ width: 18px;
101
+ }
102
+ .np-container.disabled .np-icon {
103
+ fill: var(--np-color-surface);
104
+ }
105
+ .np-container .mark.short {
106
+ height: 5.6568542495px;
107
+ }
108
+ .np-container.checked .mark {
109
+ transition-property: none;
110
+ }
111
+ .np-container .mark {
112
+ transform: scaleY(-1) translate(7px, -14px) rotate(45deg);
113
+ }
114
+ .np-container.checked .mark {
115
+ animation-duration: 350ms;
116
+ animation-timing-function: cubic-bezier(0.05, 0.7, 0.1, 1);
117
+ transition-duration: 350ms;
118
+ transition-timing-function: cubic-bezier(0.05, 0.7, 0.1, 1);
119
+ }
120
+ .mark.short {
121
+ height: 2px;
122
+ transition-property: transform, height;
123
+ width: 2px;
124
+ }
125
+ .mark {
126
+ animation-duration: 150ms;
127
+ animation-timing-function: cubic-bezier(0.3, 0, 0.8, 0.15);
128
+ transition-duration: 150ms;
129
+ transition-timing-function: cubic-bezier(0.3, 0, 0.8, 0.15);
130
+ }
131
+ .np-container.checked .mark.long {
132
+ animation-name: prev-unselected-to-checked;
133
+ }
134
+ .np-container .mark.long {
135
+ width: 11.313708499px;
136
+ }
137
+ .mark.long {
138
+ height: 2px;
139
+ transition-property: transform, width;
140
+ width: 10px;
141
+ }
142
+ @keyframes prev-unselected-to-checked {
143
+ from {
144
+ width: 0;
145
+ }
146
+ }
147
+ @media (forced-colors: active) {
148
+ .np-background {
149
+ background-color: CanvasText;
150
+ }
151
+
152
+ .np-container.disabled.checked .np-background {
153
+ background-color: GrayText;
154
+ opacity: 1;
155
+ }
156
+
157
+ .np-outline {
158
+ border-color: CanvasText;
159
+ }
160
+
161
+ .np-container.disabled .np-outline {
162
+ border-color: GrayText;
163
+ opacity: 1;
164
+ }
165
+
166
+ .np-icon {
167
+ fill: Canvas;
168
+ }
169
+ }
170
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ checked: boolean;
3
+ disabled?: boolean;
4
+ };
5
+ declare const Check: import("svelte").Component<$$ComponentProps, {}, "checked">;
6
+ type Check = ReturnType<typeof Check>;
7
+ export default Check;
@@ -1,9 +1,11 @@
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 type { SelectProps } from './types.ts'
4
+ import type { SelectOption, SelectProps } from './types.ts'
5
5
  import Item from '../list/Item.svelte'
6
6
  import { tick } from 'svelte'
7
+ import Check from './Check.svelte'
8
+ import VirtualList from './VirtualList.svelte'
7
9
 
8
10
  let {
9
11
  options = [],
@@ -27,10 +29,19 @@
27
29
  autofocus,
28
30
  onchange,
29
31
  oninput,
32
+ multiple,
30
33
  ...attributes
31
34
  }: SelectProps = $props()
32
35
 
33
36
  const uid = $props.id()
37
+ if (value === undefined) {
38
+ if (multiple) {
39
+ value = options.filter((option) => option.selected).map((option) => option.value)
40
+ } else {
41
+ value = options.find((option) => option.selected)?.value
42
+ }
43
+ }
44
+ let selectedOption = $state(options.filter((option) => option.selected))
34
45
 
35
46
  let errorTextRaw: string = $state(errorText)
36
47
  let errorRaw = $state(error)
@@ -40,11 +51,24 @@
40
51
  let field: HTMLDivElement | undefined = $state()
41
52
  let clientWidth = $state(0)
42
53
  let menuOpen = $state(false)
43
- let selectedLabel = $derived.by<string>(() => {
54
+ let selectedLabel = $derived.by<string | string[]>(() => {
55
+ if (multiple) {
56
+ if (value && Array.isArray(value)) {
57
+ return value
58
+ .map((v) => options.find((option) => option.value === v)?.label || '')
59
+ .filter((o) => o)
60
+ .join(', ')
61
+ }
62
+ return ''
63
+ }
44
64
  return options.find((option) => option.value === value)?.label || ''
45
65
  })
46
66
  $effect(() => {
47
- if (value !== '' && selectElement?.checkValidity()) {
67
+ if (
68
+ value &&
69
+ (Array.isArray(value) ? value.length !== 0 : value !== '') &&
70
+ selectElement?.checkValidity()
71
+ ) {
48
72
  errorRaw = error
49
73
  errorTextRaw = errorText
50
74
  }
@@ -56,6 +80,30 @@
56
80
  })
57
81
  }
58
82
  })
83
+ const handleOptionSelect = (event: Event, option: SelectOption) => {
84
+ if (multiple) {
85
+ if (Array.isArray(value)) {
86
+ if (value.includes(option.value)) {
87
+ selectedOption = selectedOption.filter((v) => v.value !== option.value)
88
+ value = value.filter((v) => v !== option.value)
89
+ } else {
90
+ selectedOption = [...selectedOption, option]
91
+ value = [...value, option.value]
92
+ }
93
+ } else {
94
+ selectedOption = [option]
95
+ value = [option.value]
96
+ }
97
+ } else {
98
+ selectedOption = [option]
99
+ value = option.value
100
+ menuElement?.hidePopover()
101
+ }
102
+ event.preventDefault()
103
+ tick().then(() => {
104
+ selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
105
+ })
106
+ }
59
107
  </script>
60
108
 
61
109
  {#snippet arrows()}
@@ -152,34 +200,66 @@
152
200
  </div>
153
201
  {/if}
154
202
  <div class="content">
155
- <select
156
- tabindex="-1"
157
- aria-label={attributes['aria-label'] || label}
158
- {disabled}
159
- {required}
160
- {name}
161
- {form}
162
- {onchange}
163
- {oninput}
164
- oninvalid={(event) => {
165
- event.preventDefault()
166
- const { currentTarget } = event
167
- errorRaw = true
168
- if (errorText === '') {
169
- errorTextRaw = currentTarget.validationMessage
170
- }
171
- if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
172
- field?.focus()
173
- menuElement?.showPopover()
174
- }
175
- }}
176
- bind:value
177
- bind:this={selectElement}
178
- >
179
- {#each options as option, index (index)}
180
- <option value={option.value} selected={option.selected}>{option.label}</option>
181
- {/each}
182
- </select>
203
+ {#if multiple}
204
+ <select
205
+ tabindex="-1"
206
+ aria-label={attributes['aria-label'] || label}
207
+ {disabled}
208
+ {required}
209
+ {name}
210
+ {form}
211
+ multiple
212
+ {onchange}
213
+ {oninput}
214
+ oninvalid={(event) => {
215
+ event.preventDefault()
216
+ const { currentTarget } = event
217
+ errorRaw = true
218
+ if (errorText === '') {
219
+ errorTextRaw = currentTarget.validationMessage
220
+ }
221
+ if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
222
+ field?.focus()
223
+ menuElement?.showPopover()
224
+ }
225
+ }}
226
+ bind:value
227
+ bind:this={selectElement}
228
+ >
229
+ {#each selectedOption as option, index (index)}
230
+ <option value={option.value} selected={option.selected}>{option.label}</option>
231
+ {/each}
232
+ </select>
233
+ {:else}
234
+ <select
235
+ tabindex="-1"
236
+ aria-label={attributes['aria-label'] || label}
237
+ {disabled}
238
+ {required}
239
+ {name}
240
+ {form}
241
+ {onchange}
242
+ {oninput}
243
+ oninvalid={(event) => {
244
+ event.preventDefault()
245
+ const { currentTarget } = event
246
+ errorRaw = true
247
+ if (errorText === '') {
248
+ errorTextRaw = currentTarget.validationMessage
249
+ }
250
+ if (isFirstInvalidControlInForm(currentTarget.form, currentTarget)) {
251
+ field?.focus()
252
+ menuElement?.showPopover()
253
+ }
254
+ }}
255
+ bind:value
256
+ bind:this={selectElement}
257
+ >
258
+ {#each selectedOption as option, index (index)}
259
+ <option value={option.value} selected={option.selected}>{option.label}</option>
260
+ {/each}
261
+ </select>
262
+ {/if}
183
263
  <div class="input">
184
264
  {#if selectedLabel}
185
265
  {selectedLabel}
@@ -208,6 +288,41 @@
208
288
  </div>
209
289
  </div>
210
290
 
291
+ {#snippet item(option: SelectOption, width?: number)}
292
+ <Item
293
+ onclick={(event) => {
294
+ handleOptionSelect(event, option)
295
+ field?.focus()
296
+ }}
297
+ disabled={option.disabled}
298
+ onkeydown={(event) => {
299
+ if (event.key === 'ArrowDown') {
300
+ ;(event.currentTarget?.nextElementSibling as HTMLElement)?.focus()
301
+ event.preventDefault()
302
+ }
303
+ if (event.key === 'ArrowUp') {
304
+ ;(event.currentTarget?.previousElementSibling as HTMLElement)?.focus()
305
+ event.preventDefault()
306
+ }
307
+ if (event.key === 'Enter') {
308
+ handleOptionSelect(event, option)
309
+ }
310
+ if (event.key === 'Tab') {
311
+ menuElement?.hidePopover()
312
+ }
313
+ }}
314
+ variant="button"
315
+ style={width ? `width:${width}px` : ''}
316
+ selected={Array.isArray(value) ? value.includes(option.value) : value === option.value}
317
+ >{option.label}
318
+ {#snippet start()}
319
+ {#if Array.isArray(value) && multiple}
320
+ <Check disabled={option.disabled} checked={value.includes(option.value)} />
321
+ {/if}
322
+ {/snippet}
323
+ </Item>
324
+ {/snippet}
325
+
211
326
  <Menu
212
327
  style="position-anchor:--{uid};min-width:{clientWidth}px;"
213
328
  popover="manual"
@@ -228,43 +343,17 @@
228
343
  }}
229
344
  bind:element={menuElement}
230
345
  >
231
- {#each options as option, index (index)}
232
- <Item
233
- onclick={(event) => {
234
- value = option.value
235
- menuElement?.hidePopover()
236
- field?.focus()
237
- event.preventDefault()
238
- tick().then(() => {
239
- selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
240
- })
241
- }}
242
- onkeydown={(event) => {
243
- if (event.key === 'ArrowDown') {
244
- ;(event.currentTarget?.nextElementSibling as HTMLElement)?.focus()
245
- event.preventDefault()
246
- }
247
- if (event.key === 'ArrowUp') {
248
- ;(event.currentTarget?.previousElementSibling as HTMLElement)?.focus()
249
- event.preventDefault()
250
- }
251
- if (event.key === 'Enter') {
252
- value = option.value
253
- menuElement?.hidePopover()
254
- event.preventDefault()
255
- tick().then(() => {
256
- selectElement?.dispatchEvent(new Event('change', { bubbles: true }))
257
- })
258
- }
259
- if (event.key === 'Tab') {
260
- menuElement?.hidePopover()
261
- }
262
- }}
263
- variant="button"
264
- selected={value === option.value}
265
- >{option.label}
266
- </Item>
267
- {/each}
346
+ {#if options.length > 100}
347
+ <VirtualList width="{clientWidth}px" height="250px" itemHeight={56} items={options}>
348
+ {#snippet row(option)}
349
+ {@render item(option, clientWidth - 15)}
350
+ {/snippet}
351
+ </VirtualList>
352
+ {:else}
353
+ {#each options as option, index (index)}
354
+ {@render item(option)}
355
+ {/each}
356
+ {/if}
268
357
  </Menu>
269
358
 
270
359
  <style>
@@ -430,6 +519,8 @@
430
519
 
431
520
  .content select {
432
521
  width: 0;
522
+ height: 0;
523
+ visibility: hidden;
433
524
  }
434
525
 
435
526
  .middle {
@@ -0,0 +1,181 @@
1
+ <script lang="ts" generics="T">
2
+ import { onMount, tick, type Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ interface VitualListProps extends HTMLAttributes<HTMLDivElement> {
6
+ items: T[]
7
+ height?: string
8
+ width?: string
9
+ itemHeight?: number
10
+ start?: number
11
+ end?: number
12
+ row: Snippet<[T]>
13
+ }
14
+ // props
15
+ let {
16
+ items,
17
+ height = '100%',
18
+ width,
19
+ itemHeight,
20
+ start = $bindable(0),
21
+ end = $bindable(0),
22
+ row,
23
+ }: VitualListProps = $props()
24
+
25
+ // local state
26
+ let height_map: number[] = []
27
+ // eslint-disable-next-line no-undef
28
+ let rows: HTMLCollectionOf<HTMLElement> | undefined = $state()
29
+ let viewport: HTMLElement | undefined = $state()
30
+ let contents: HTMLDivElement | undefined = $state()
31
+ let viewport_height: number = $state(0)
32
+ let mounted = $state()
33
+
34
+ let top = $state(0)
35
+ let bottom = $state(0)
36
+ let average_height: number = $state(0)
37
+
38
+ $effect(() => {
39
+ if (mounted) {
40
+ refresh(items, viewport_height, itemHeight)
41
+ }
42
+ })
43
+ let visible = $derived(
44
+ items.slice(start, end).map((data, i) => {
45
+ return { index: i + start, data }
46
+ }),
47
+ )
48
+
49
+ async function refresh(items: T[], viewport_height: number, itemHeight?: number) {
50
+ if (!viewport || !rows) {
51
+ return
52
+ }
53
+ const { scrollTop } = viewport
54
+
55
+ await tick() // wait until the DOM is up to date
56
+
57
+ let content_height = top - scrollTop
58
+ let i = start
59
+
60
+ while (content_height < viewport_height && i < items.length) {
61
+ let row = rows[i - start]
62
+
63
+ if (!row) {
64
+ end = i + 1
65
+ await tick() // render the newly visible row
66
+ row = rows[i - start]
67
+ }
68
+
69
+ const row_height = (height_map[i] = itemHeight || row.offsetHeight)
70
+ content_height += row_height
71
+ i += 1
72
+ }
73
+
74
+ end = i
75
+
76
+ const remaining = items.length - end
77
+ average_height = (top + content_height) / end
78
+
79
+ bottom = remaining * average_height
80
+ height_map.length = items.length
81
+ }
82
+
83
+ async function handle_scroll() {
84
+ if (!viewport || !rows) {
85
+ return
86
+ }
87
+
88
+ const { scrollTop } = viewport
89
+
90
+ const old_start = start
91
+
92
+ for (let v = 0; v < rows.length; v += 1) {
93
+ height_map[start + v] = itemHeight || rows[v].offsetHeight
94
+ }
95
+
96
+ let i = 0
97
+ let y = 0
98
+
99
+ while (i < items.length) {
100
+ const row_height = height_map[i] || average_height
101
+ if (y + row_height > scrollTop) {
102
+ start = i
103
+ top = y
104
+
105
+ break
106
+ }
107
+
108
+ y += row_height
109
+ i += 1
110
+ }
111
+
112
+ while (i < items.length) {
113
+ y += height_map[i] || average_height
114
+ i += 1
115
+
116
+ if (y > scrollTop + viewport_height) break
117
+ }
118
+
119
+ end = i
120
+
121
+ const remaining = items.length - end
122
+ average_height = y / end
123
+
124
+ height_map.fill(average_height, i, items.length)
125
+ bottom = remaining * average_height
126
+
127
+ // prevent jumping if we scrolled up into unknown territory
128
+ if (start < old_start) {
129
+ await tick()
130
+
131
+ let expected_height = 0
132
+ let actual_height = 0
133
+
134
+ for (let i = start; i < old_start; i += 1) {
135
+ if (rows[i - start]) {
136
+ expected_height += height_map[i]
137
+ actual_height += itemHeight || rows[i - start].offsetHeight
138
+ }
139
+ }
140
+
141
+ const d = actual_height - expected_height
142
+ viewport.scrollTo(0, scrollTop + d)
143
+ }
144
+
145
+ // TODO if we overestimated the space these
146
+ // rows would occupy we may need to add some
147
+ // more. maybe we can just call handle_scroll again?
148
+ }
149
+
150
+ // trigger initial refresh
151
+ onMount(() => {
152
+ // eslint-disable-next-line no-undef
153
+ rows = contents?.children as HTMLCollectionOf<HTMLElement>
154
+ mounted = true
155
+ })
156
+ </script>
157
+
158
+ <svelte-virtual-list-viewport
159
+ bind:this={viewport}
160
+ bind:offsetHeight={viewport_height}
161
+ onscroll={handle_scroll}
162
+ style="display:flex;height: {height};width: {width};"
163
+ >
164
+ <div
165
+ bind:this={contents}
166
+ style="flex:1;display:block;padding-top: {top}px; padding-bottom: {bottom}px;"
167
+ >
168
+ {#each visible as entry (entry.index)}
169
+ {@render row(entry.data)}
170
+ {/each}
171
+ </div>
172
+ </svelte-virtual-list-viewport>
173
+
174
+ <style>
175
+ svelte-virtual-list-viewport {
176
+ position: relative;
177
+ overflow-y: auto;
178
+ -webkit-overflow-scrolling: touch;
179
+ display: block;
180
+ }
181
+ </style>
@@ -0,0 +1,27 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ declare class __sveltets_Render<T> {
4
+ props(): HTMLAttributes<HTMLDivElement> & {
5
+ items: T[];
6
+ height?: string;
7
+ width?: string;
8
+ itemHeight?: number;
9
+ start?: number;
10
+ end?: number;
11
+ row: Snippet<[T]>;
12
+ };
13
+ events(): {};
14
+ slots(): {};
15
+ bindings(): "start" | "end";
16
+ exports(): {};
17
+ }
18
+ interface $$IsomorphicComponent {
19
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
20
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
21
+ } & ReturnType<__sveltets_Render<T>['exports']>;
22
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
23
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
24
+ }
25
+ declare const VirtualList: $$IsomorphicComponent;
26
+ type VirtualList<T> = InstanceType<typeof VirtualList<T>>;
27
+ export default VirtualList;
@@ -1,6 +1,12 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLSelectAttributes } from 'svelte/elements';
3
- export interface SelectProps extends Omit<HTMLSelectAttributes, 'multiple' | 'size' | 'autocomplete'> {
3
+ export interface SelectOption {
4
+ value: string | number;
5
+ label: string;
6
+ disabled?: boolean;
7
+ selected?: boolean | undefined | null;
8
+ }
9
+ export interface SelectProps extends Omit<HTMLSelectAttributes, 'size' | 'autocomplete'> {
4
10
  label?: string;
5
11
  supportingText?: string;
6
12
  error?: boolean;
@@ -10,9 +16,5 @@ export interface SelectProps extends Omit<HTMLSelectAttributes, 'multiple' | 'si
10
16
  end?: Snippet;
11
17
  noAsterisk?: boolean;
12
18
  element?: HTMLSpanElement;
13
- options: {
14
- value: string | number;
15
- label: string;
16
- selected?: boolean | undefined | null;
17
- }[];
19
+ options: SelectOption[];
18
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {