noph-ui 0.17.12 → 0.18.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.
@@ -2,7 +2,6 @@
2
2
  import Item from '../list/Item.svelte'
3
3
  import Menu from '../menu/Menu.svelte'
4
4
  import VirtualList from '../select/VirtualList.svelte'
5
- import { tick } from 'svelte'
6
5
  import type { AutoCompleteOption, AutoCompleteProps } from './types.ts'
7
6
  import TextField from '../text-field/TextField.svelte'
8
7
 
@@ -11,56 +10,38 @@
11
10
  value = $bindable(),
12
11
  variant = 'filled',
13
12
  element = $bindable(),
14
- onchange,
15
- oninput,
13
+ populated,
16
14
  reportValidity = $bindable(),
17
15
  checkValidity = $bindable(),
18
16
  clampMenuWidth = false,
19
17
  children,
20
- optionsFilter = (option) => {
21
- return !value || option.value.includes(value)
22
- },
18
+ optionsFilter,
23
19
  oncomplete = (option) => {
24
- value = option.value
25
- console.log(menuElement)
20
+ value = option.label
21
+ finalPopulated = populated
26
22
  menuElement?.hidePopover()
27
23
  },
28
- onblur,
24
+ onkeydown,
29
25
  onclick,
26
+ oninput,
30
27
  ...attributes
31
28
  }: AutoCompleteProps = $props()
32
29
 
33
30
  const uid = $props.id()
34
- let displayOptions = $derived(options.filter(optionsFilter))
31
+ let defaultOptionsFilter = (option: AutoCompleteOption) => {
32
+ return !value || option.label.includes(value)
33
+ }
34
+ let displayOptions = $derived(options.filter(optionsFilter || defaultOptionsFilter))
35
35
  let useVirtualList = $derived(displayOptions.length > 4000)
36
36
  let clientWidth = $state(0)
37
- let menuOpen = $state(false)
38
37
  let menuElement: HTMLDivElement | undefined = $state()
39
-
40
- const handleOptionSelect = (event: Event, option: AutoCompleteOption) => {
41
- value = option.value
42
- menuElement?.hidePopover()
43
- event.preventDefault()
44
- // tick().then(() => {
45
- // if (checkValidity?.()) {
46
- // errorRaw = error
47
- // errorTextRaw = errorText
48
- // }
49
- // element?.dispatchEvent(new Event('change', { bubbles: true }))
50
- // })
51
- }
52
- $effect(() => {
53
- if (displayOptions.length) {
54
- menuElement?.showPopover()
55
- } else {
56
- menuElement?.hidePopover()
57
- }
58
- })
38
+ let finalPopulated = $state(populated)
59
39
  </script>
60
40
 
61
41
  {#snippet item(option: AutoCompleteOption)}
62
42
  <Item
63
- onclick={() => {
43
+ onclick={(event) => {
44
+ event.preventDefault()
64
45
  oncomplete(option)
65
46
  element?.focus()
66
47
  }}
@@ -78,44 +59,56 @@
78
59
  oncomplete(option)
79
60
  }
80
61
  if (event.key === 'Tab') {
62
+ finalPopulated = populated
81
63
  menuElement?.hidePopover()
82
64
  }
83
65
  }}
84
66
  variant="button"
85
- >{option.value}
67
+ >{option.label}
86
68
  </Item>
87
69
  {/snippet}
88
70
 
89
71
  <TextField
90
72
  {variant}
73
+ type="text"
74
+ populated={finalPopulated}
91
75
  bind:clientWidth
92
76
  bind:value
93
77
  style="anchor-name:--{uid};"
94
- onclick={async (
95
- event: MouseEvent & {
96
- currentTarget: EventTarget & HTMLInputElement
97
- },
98
- ) => {
99
- if (displayOptions.length) {
100
- menuElement?.showPopover()
101
- }
78
+ onclick={(event) => {
79
+ finalPopulated = true
80
+ menuElement?.showPopover()
102
81
  onclick?.(event)
103
82
  }}
104
- onfocus={() => {
105
- if (displayOptions.length) {
106
- menuElement?.showPopover()
83
+ oninput={(event) => {
84
+ menuElement?.showPopover()
85
+ oninput?.(event)
86
+ }}
87
+ onkeydown={(event) => {
88
+ if (event.key === 'Tab' || event.key === 'Escape') {
89
+ menuElement?.hidePopover()
90
+ } else {
91
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
92
+ event.preventDefault()
93
+ finalPopulated = true
94
+ menuElement?.showPopover()
95
+ ;(menuElement?.firstElementChild?.firstElementChild as HTMLElement)?.focus()
96
+ }
107
97
  }
98
+ onkeydown?.(event)
108
99
  }}
109
100
  bind:reportValidity
110
101
  bind:checkValidity
111
102
  bind:element
112
- {...attributes}>{@render children?.()}</TextField
113
- >
103
+ {...attributes}
104
+ >{@render children?.()}
105
+ </TextField>
114
106
  <Menu
115
107
  style="position-anchor:--{uid};{clampMenuWidth || useVirtualList
116
108
  ? 'width'
117
109
  : 'min-width'}:{clientWidth}px"
118
110
  role="listbox"
111
+ class={[!displayOptions.length && 'np-auto-complete-empty']}
119
112
  --np-menu-justify-self="none"
120
113
  --np-menu-position-area="bottom span-right"
121
114
  --np-menu-margin="2px 0"
@@ -123,13 +116,6 @@
123
116
  ? 'var(--np-outlined-select-text-field-container-shape)'
124
117
  : 'var(--np-filled-select-text-field-container-shape)'}
125
118
  anchor={element}
126
- ontoggle={({ newState }) => {
127
- if (newState === 'open') {
128
- menuOpen = true
129
- } else {
130
- menuOpen = false
131
- }
132
- }}
133
119
  bind:element={menuElement}
134
120
  >
135
121
  {#if useVirtualList}
@@ -144,3 +130,10 @@
144
130
  {/each}
145
131
  {/if}
146
132
  </Menu>
133
+
134
+ <style>
135
+ :global(.np-auto-complete-empty) {
136
+ display: none !important;
137
+ opacity: 0 !important;
138
+ }
139
+ </style>
@@ -1,6 +1,7 @@
1
1
  import type { InputFieldProps } from '../types.ts';
2
2
  export interface AutoCompleteOption {
3
- value: string;
3
+ value?: string | number;
4
+ label: string;
4
5
  disabled?: boolean;
5
6
  selected?: boolean | undefined | null;
6
7
  }
@@ -41,7 +41,7 @@ export interface InputChipProps extends HTMLAttributes<HTMLDivElement> {
41
41
  ariaLabelRemove?: string;
42
42
  element?: HTMLDivElement;
43
43
  name?: string;
44
- value?: string;
44
+ value?: string | number;
45
45
  onremove?: (event: MouseEvent & {
46
46
  currentTarget: EventTarget & HTMLButtonElement;
47
47
  }) => void;
@@ -367,7 +367,6 @@
367
367
  style="position-anchor:--{uid};{clampMenuWidth || useVirtualList
368
368
  ? 'width'
369
369
  : 'min-width'}:{clientWidth}px"
370
- popover="manual"
371
370
  role="listbox"
372
371
  --np-menu-justify-self="none"
373
372
  --np-menu-position-area="bottom span-right"
@@ -35,11 +35,16 @@
35
35
  const oldIndicatorRect = oldIndicator?.getBoundingClientRect()
36
36
  if (oldIndicatorRect) {
37
37
  const newIndicator = el.querySelector<HTMLElement>('.np-indicator')
38
- newIndicator?.style.setProperty(
39
- '--np-tab-indicator-start',
40
- `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
41
- )
42
- newIndicator?.style.setProperty('--np-tab-indicator-width', `${oldIndicatorRect.width}px`)
38
+ if (newIndicator) {
39
+ newIndicator.style.setProperty(
40
+ '--np-tab-indicator-start',
41
+ `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
42
+ )
43
+ newIndicator.style.setProperty(
44
+ '--np-tab-indicator-scale',
45
+ `${oldIndicatorRect.width / newIndicator.clientWidth}`,
46
+ )
47
+ }
43
48
  }
44
49
  activeTab.value = value
45
50
  activeTab.node = el
@@ -99,12 +104,7 @@
99
104
  role="tab"
100
105
  bind:this={element}
101
106
  {href}
102
- class={[
103
- 'np-tab',
104
- variant === 'secondary' && 'np-tab-secondary',
105
- isActive && 'np-tab-content-active',
106
- attributes.class,
107
- ]}
107
+ class={['np-tab', isActive && 'np-tab-content-active', attributes.class]}
108
108
  onclick={onClick}
109
109
  onkeydown={onKeyDown}
110
110
  >
@@ -117,12 +117,7 @@
117
117
  tabindex={isActive ? 0 : -1}
118
118
  role="tab"
119
119
  bind:this={element}
120
- class={[
121
- 'np-tab',
122
- variant === 'secondary' && 'np-tab-secondary',
123
- isActive && 'np-tab-content-active',
124
- attributes.class,
125
- ]}
120
+ class={['np-tab', isActive && 'np-tab-content-active', attributes.class]}
126
121
  onclick={onClick}
127
122
  onkeydown={onKeyDown}
128
123
  >
@@ -188,17 +183,16 @@
188
183
  }
189
184
  .np-tab-content-active .np-indicator {
190
185
  opacity: 1;
186
+ transform-origin: left center;
191
187
  animation: slide 0.3s ease-in-out;
192
188
  }
193
189
 
194
190
  @keyframes slide {
195
191
  0% {
196
- width: var(--np-tab-indicator-width, 100%);
197
- transform: translateX(var(--np-tab-indicator-start));
192
+ transform: translateX(var(--np-tab-indicator-start)) scaleX(var(--np-tab-indicator-scale, 1));
198
193
  }
199
194
  100% {
200
- width: 100%;
201
- transform: translateX(0);
195
+ transform: translateX(0) scaleX(1);
202
196
  }
203
197
  }
204
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.17.12",
3
+ "version": "0.18.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {