noph-ui 0.17.10 → 0.17.12

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.
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import Item from '../list/Item.svelte'
3
+ import Menu from '../menu/Menu.svelte'
4
+ import VirtualList from '../select/VirtualList.svelte'
5
+ import { tick } from 'svelte'
6
+ import type { AutoCompleteOption, AutoCompleteProps } from './types.ts'
7
+ import TextField from '../text-field/TextField.svelte'
8
+
9
+ let {
10
+ options = [],
11
+ value = $bindable(),
12
+ variant = 'filled',
13
+ element = $bindable(),
14
+ onchange,
15
+ oninput,
16
+ reportValidity = $bindable(),
17
+ checkValidity = $bindable(),
18
+ clampMenuWidth = false,
19
+ children,
20
+ optionsFilter = (option) => {
21
+ return !value || option.value.includes(value)
22
+ },
23
+ oncomplete = (option) => {
24
+ value = option.value
25
+ console.log(menuElement)
26
+ menuElement?.hidePopover()
27
+ },
28
+ onblur,
29
+ onclick,
30
+ ...attributes
31
+ }: AutoCompleteProps = $props()
32
+
33
+ const uid = $props.id()
34
+ let displayOptions = $derived(options.filter(optionsFilter))
35
+ let useVirtualList = $derived(displayOptions.length > 4000)
36
+ let clientWidth = $state(0)
37
+ let menuOpen = $state(false)
38
+ 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
+ })
59
+ </script>
60
+
61
+ {#snippet item(option: AutoCompleteOption)}
62
+ <Item
63
+ onclick={() => {
64
+ oncomplete(option)
65
+ element?.focus()
66
+ }}
67
+ disabled={option.disabled}
68
+ onkeydown={(event) => {
69
+ if (event.key === 'ArrowDown') {
70
+ ;(event.currentTarget?.nextElementSibling as HTMLElement)?.focus()
71
+ event.preventDefault()
72
+ }
73
+ if (event.key === 'ArrowUp') {
74
+ ;(event.currentTarget?.previousElementSibling as HTMLElement)?.focus()
75
+ event.preventDefault()
76
+ }
77
+ if (event.key === 'Enter') {
78
+ oncomplete(option)
79
+ }
80
+ if (event.key === 'Tab') {
81
+ menuElement?.hidePopover()
82
+ }
83
+ }}
84
+ variant="button"
85
+ >{option.value}
86
+ </Item>
87
+ {/snippet}
88
+
89
+ <TextField
90
+ {variant}
91
+ bind:clientWidth
92
+ bind:value
93
+ style="anchor-name:--{uid};"
94
+ onclick={async (
95
+ event: MouseEvent & {
96
+ currentTarget: EventTarget & HTMLInputElement
97
+ },
98
+ ) => {
99
+ if (displayOptions.length) {
100
+ menuElement?.showPopover()
101
+ }
102
+ onclick?.(event)
103
+ }}
104
+ onfocus={() => {
105
+ if (displayOptions.length) {
106
+ menuElement?.showPopover()
107
+ }
108
+ }}
109
+ bind:reportValidity
110
+ bind:checkValidity
111
+ bind:element
112
+ {...attributes}>{@render children?.()}</TextField
113
+ >
114
+ <Menu
115
+ style="position-anchor:--{uid};{clampMenuWidth || useVirtualList
116
+ ? 'width'
117
+ : 'min-width'}:{clientWidth}px"
118
+ role="listbox"
119
+ --np-menu-justify-self="none"
120
+ --np-menu-position-area="bottom span-right"
121
+ --np-menu-margin="2px 0"
122
+ --np-menu-container-shape={variant === 'outlined'
123
+ ? 'var(--np-outlined-select-text-field-container-shape)'
124
+ : 'var(--np-filled-select-text-field-container-shape)'}
125
+ anchor={element}
126
+ ontoggle={({ newState }) => {
127
+ if (newState === 'open') {
128
+ menuOpen = true
129
+ } else {
130
+ menuOpen = false
131
+ }
132
+ }}
133
+ bind:element={menuElement}
134
+ >
135
+ {#if useVirtualList}
136
+ <VirtualList height="250px" itemHeight={56} items={displayOptions}>
137
+ {#snippet row(option)}
138
+ {@render item(option)}
139
+ {/snippet}
140
+ </VirtualList>
141
+ {:else}
142
+ {#each displayOptions as option, index (index)}
143
+ {@render item(option)}
144
+ {/each}
145
+ {/if}
146
+ </Menu>
@@ -0,0 +1,4 @@
1
+ import type { AutoCompleteProps } from './types.ts';
2
+ declare const AutoComplete: import("svelte").Component<AutoCompleteProps, {}, "element" | "value" | "reportValidity" | "checkValidity">;
3
+ type AutoComplete = ReturnType<typeof AutoComplete>;
4
+ export default AutoComplete;
@@ -0,0 +1 @@
1
+ export { default as AutoComplete } from './AutoComplete.svelte';
@@ -0,0 +1 @@
1
+ export { default as AutoComplete } from './AutoComplete.svelte';
@@ -0,0 +1,12 @@
1
+ import type { InputFieldProps } from '../types.ts';
2
+ export interface AutoCompleteOption {
3
+ value: string;
4
+ disabled?: boolean;
5
+ selected?: boolean | undefined | null;
6
+ }
7
+ export interface AutoCompleteProps extends Omit<InputFieldProps, 'clientWidth' | 'clientHeight'> {
8
+ options: AutoCompleteOption[];
9
+ optionsFilter?: (option: AutoCompleteOption) => boolean;
10
+ oncomplete?: (option: AutoCompleteOption) => void;
11
+ clampMenuWidth?: boolean;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
1
  import type { CheckboxProps } from './types.ts';
2
- declare const Checkbox: import("svelte").Component<CheckboxProps, {}, "element" | "group" | "indeterminate" | "checked">;
2
+ declare const Checkbox: import("svelte").Component<CheckboxProps, {}, "element" | "group" | "checked" | "indeterminate">;
3
3
  type Checkbox = ReturnType<typeof Checkbox>;
4
4
  export default Checkbox;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './autocomplete/index.js';
1
2
  export * from './button/index.js';
2
3
  export * from './card/index.js';
3
4
  export * from './checkbox/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './autocomplete/index.js';
1
2
  export * from './button/index.js';
2
3
  export * from './card/index.js';
3
4
  export * from './checkbox/index.js';
@@ -8,6 +8,7 @@
8
8
  showPopover = $bindable(),
9
9
  hidePopover = $bindable(),
10
10
  style,
11
+ popover = 'auto',
11
12
  ...attributes
12
13
  }: MenuProps = $props()
13
14
 
@@ -119,7 +120,7 @@
119
120
  menuOpen = newState === 'open'
120
121
  attributes.ontoggle?.(event)
121
122
  }}
122
- popover="auto"
123
+ {popover}
123
124
  class={['np-menu-container', attributes.class]}
124
125
  >
125
126
  <div class="np-menu">
@@ -15,7 +15,19 @@
15
15
  ...attributes
16
16
  }: TabProps = $props()
17
17
  let activeTab: { value: string | number; node: HTMLElement } = getContext('activeTab')
18
- let isActive = $derived(activeTab.value === value)
18
+ let element: HTMLElement | undefined = $state()
19
+ let isActive = $state(activeTab.value === value)
20
+
21
+ $effect(() => {
22
+ if (activeTab.value === value) {
23
+ if (element && element !== activeTab.node) {
24
+ setTabActive(element)
25
+ }
26
+ isActive = true
27
+ } else {
28
+ isActive = false
29
+ }
30
+ })
19
31
 
20
32
  const setTabActive = (el: HTMLElement) => {
21
33
  const oldTab = activeTab.node as HTMLElement | undefined
@@ -27,10 +39,12 @@
27
39
  '--np-tab-indicator-start',
28
40
  `${oldIndicatorRect.x - newIndicator.getBoundingClientRect().x}px`,
29
41
  )
42
+ newIndicator?.style.setProperty('--np-tab-indicator-width', `${oldIndicatorRect.width}px`)
30
43
  }
31
44
  activeTab.value = value
32
45
  activeTab.node = el
33
46
  }
47
+
34
48
  const setActiveTab = (el: HTMLElement) => {
35
49
  if (isActive) {
36
50
  activeTab.node = el
@@ -83,6 +97,7 @@
83
97
  {...attributes}
84
98
  tabindex={isActive ? 0 : -1}
85
99
  role="tab"
100
+ bind:this={element}
86
101
  {href}
87
102
  class={[
88
103
  'np-tab',
@@ -101,6 +116,7 @@
101
116
  {...attributes}
102
117
  tabindex={isActive ? 0 : -1}
103
118
  role="tab"
119
+ bind:this={element}
104
120
  class={[
105
121
  'np-tab',
106
122
  variant === 'secondary' && 'np-tab-secondary',
@@ -177,9 +193,11 @@
177
193
 
178
194
  @keyframes slide {
179
195
  0% {
196
+ width: var(--np-tab-indicator-width, 100%);
180
197
  transform: translateX(var(--np-tab-indicator-start));
181
198
  }
182
199
  100% {
200
+ width: 100%;
183
201
  transform: translateX(0);
184
202
  }
185
203
  }
@@ -13,6 +13,9 @@
13
13
  $effect(() => {
14
14
  value = active.value
15
15
  })
16
+ $effect(() => {
17
+ active.value = value
18
+ })
16
19
  </script>
17
20
 
18
21
  <div {...attributes} class={[attributes.class]}>
@@ -24,6 +24,8 @@
24
24
  children,
25
25
  onfocus,
26
26
  onblur,
27
+ clientWidth = $bindable(),
28
+ clientHeight = $bindable(),
27
29
  ...attributes
28
30
  }: TextFieldProps = $props()
29
31
 
@@ -95,6 +97,7 @@
95
97
  })
96
98
  </script>
97
99
 
100
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
98
101
  <div
99
102
  style={(variant === 'outlined'
100
103
  ? '--_label-text-color:var(--np-outlined-text-field-label-text-color);--top-space:1rem;--bottom-space:1rem;--floating-label-top:-0.5rem;--floating-label-left:-2.25rem;--_focus-outline-width:3px;'
@@ -104,11 +107,16 @@
104
107
  (children ? '--top-space:2rem;--bottom-space:1rem;' : '')) + style}
105
108
  class={['text-field', attributes.class]}
106
109
  bind:this={element}
110
+ bind:clientWidth
111
+ bind:clientHeight
107
112
  role="button"
108
- tabindex="-1"
113
+ tabindex="0"
109
114
  onfocus={() => {
110
115
  inputElement?.focus()
111
116
  }}
117
+ onclick={() => {
118
+ inputElement?.click()
119
+ }}
112
120
  >
113
121
  <div
114
122
  class="field"
@@ -403,6 +411,7 @@
403
411
 
404
412
  .input-wrapper {
405
413
  display: flex;
414
+ flex-wrap: wrap;
406
415
  align-items: baseline;
407
416
  min-width: 0;
408
417
  }
@@ -484,7 +493,7 @@
484
493
  margin-bottom: var(--bottom-space, 0.5rem);
485
494
  }
486
495
  .content .input-wrapper .input {
487
- min-width: 0;
496
+ min-width: 20px;
488
497
  }
489
498
  :global(.content .input-wrapper .np-chip-set) {
490
499
  margin-top: calc(var(--top-space, 1.5rem) - 4px);
@@ -1,4 +1,4 @@
1
1
  import type { TextFieldProps } from './types.ts';
2
- declare const TextField: import("svelte").Component<TextFieldProps, {}, "element" | "value" | "reportValidity" | "checkValidity" | "inputElement">;
2
+ declare const TextField: import("svelte").Component<TextFieldProps, {}, "element" | "value" | "reportValidity" | "checkValidity" | "inputElement" | "clientWidth" | "clientHeight">;
3
3
  type TextField = ReturnType<typeof TextField>;
4
4
  export default TextField;
@@ -16,6 +16,8 @@ interface FieldProps {
16
16
  populated?: boolean;
17
17
  reportValidity?: () => boolean;
18
18
  checkValidity?: () => boolean;
19
+ clientWidth?: number;
20
+ clientHeight?: number;
19
21
  }
20
22
  export interface InputFieldProps extends HTMLInputAttributes, FieldProps {
21
23
  type?: 'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url' | 'datetime-local';
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './autocomplete/types.js';
1
2
  export * from './button/types.ts';
2
3
  export * from './card/types.ts';
3
4
  export * from './checkbox/types.ts';
package/dist/types.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './autocomplete/types.js';
1
2
  export * from './button/types.ts';
2
3
  export * from './card/types.ts';
3
4
  export * from './checkbox/types.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.17.10",
3
+ "version": "0.17.12",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {
@@ -53,27 +53,27 @@
53
53
  "svelte": "^5.32.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@eslint/js": "^9.28.0",
56
+ "@eslint/js": "^9.29.0",
57
57
  "@material/material-color-utilities": "^0.3.0",
58
- "@playwright/test": "^1.52.0",
58
+ "@playwright/test": "^1.53.0",
59
59
  "@sveltejs/adapter-vercel": "^5.7.2",
60
- "@sveltejs/kit": "^2.21.2",
60
+ "@sveltejs/kit": "^2.21.5",
61
61
  "@sveltejs/package": "^2.3.11",
62
62
  "@sveltejs/vite-plugin-svelte": "^5.1.0",
63
63
  "@types/eslint": "^9.6.1",
64
- "eslint": "^9.28.0",
64
+ "eslint": "^9.29.0",
65
65
  "eslint-config-prettier": "^10.1.5",
66
- "eslint-plugin-svelte": "^3.9.1",
66
+ "eslint-plugin-svelte": "^3.9.2",
67
67
  "globals": "^16.2.0",
68
68
  "prettier": "^3.5.3",
69
69
  "prettier-plugin-svelte": "^3.4.0",
70
70
  "publint": "^0.3.12",
71
- "svelte": "^5.33.16",
71
+ "svelte": "^5.34.3",
72
72
  "svelte-check": "^4.2.1",
73
73
  "typescript": "^5.8.3",
74
- "typescript-eslint": "^8.33.1",
74
+ "typescript-eslint": "^8.34.1",
75
75
  "vite": "^6.3.5",
76
- "vitest": "^3.2.2"
76
+ "vitest": "^3.2.3"
77
77
  },
78
78
  "svelte": "./dist/index.js",
79
79
  "types": "./dist/index.d.ts",