noph-ui 0.24.15 → 0.24.17

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.
@@ -212,15 +212,14 @@
212
212
  ? 'var(--np-outlined-select-text-field-container-shape)'
213
213
  : 'var(--np-filled-select-text-field-container-shape)'}
214
214
  anchor={element}
215
+ bind:open={menuOpen}
215
216
  ontoggle={(e) => {
216
217
  if (e.newState === 'closed') {
217
- menuOpen = false
218
218
  activeIndex = NO_INDEX
219
219
  if (!populated && finalPopulated && !value) {
220
220
  finalPopulated = false
221
221
  }
222
222
  } else {
223
- menuOpen = true
224
223
  if (activeIndex >= displayOptions.length) {
225
224
  activeIndex = NO_INDEX
226
225
  }
@@ -14,7 +14,6 @@
14
14
  element = $bindable(),
15
15
  disabled = false,
16
16
  loading = false,
17
- keepTooltipOnClick,
18
17
  loadingAriaLabel,
19
18
  size = 's',
20
19
  shape = 'round',
@@ -106,7 +105,7 @@
106
105
  {/if}
107
106
 
108
107
  {#if title && !disabled && !loading}
109
- <Tooltip {keepTooltipOnClick} id={uid}>{title}</Tooltip>
108
+ <Tooltip id={uid}>{title}</Tooltip>
110
109
  {/if}
111
110
 
112
111
  <style>
@@ -16,7 +16,6 @@
16
16
  loadingAriaLabel,
17
17
  selected = $bindable(false),
18
18
  selectedIcon,
19
- keepTooltipOnClick,
20
19
  width = 'default',
21
20
  size = 's',
22
21
  shape = 'round',
@@ -34,7 +33,7 @@
34
33
  <span class="np-touch" bind:this={touchEl}></span>
35
34
  {/if}
36
35
  {#if loading}
37
- <div class="circular-progress">
36
+ <div class="np-icon-button-circular-progress">
38
37
  <CircularProgress aria-label={loadingAriaLabel} indeterminate />
39
38
  </div>
40
39
  {/if}
@@ -100,7 +99,7 @@
100
99
  {/if}
101
100
 
102
101
  {#if title && !disabled && !loading}
103
- <Tooltip {keepTooltipOnClick} id={uid}>{title}</Tooltip>
102
+ <Tooltip id={uid}>{title}</Tooltip>
104
103
  {/if}
105
104
 
106
105
  <style>
@@ -365,7 +364,7 @@
365
364
  width: max(calc(var(--_button-width, 40px) + 8px), 100%);
366
365
  }
367
366
 
368
- .circular-progress {
367
+ .np-icon-button-circular-progress {
369
368
  --np-circular-progress-color: color-mix(in srgb, var(--np-color-on-surface) 38%, transparent);
370
369
  position: absolute;
371
370
  }
@@ -8,7 +8,6 @@ interface ButtonButtonProps extends HTMLButtonAttributes {
8
8
  disabled?: boolean;
9
9
  loading?: boolean;
10
10
  loadingAriaLabel?: string;
11
- keepTooltipOnClick?: boolean;
12
11
  size?: 'xs' | 's' | 'm' | 'l' | 'xl';
13
12
  toggle?: boolean;
14
13
  shape?: 'round' | 'square';
@@ -19,7 +18,6 @@ interface ButtonAnchorProps extends HTMLAnchorAttributes {
19
18
  start?: Snippet;
20
19
  end?: Snippet;
21
20
  element?: HTMLElement;
22
- keepTooltipOnClick?: boolean;
23
21
  disabled?: boolean;
24
22
  loading?: boolean;
25
23
  loadingAriaLabel?: string;
@@ -35,7 +33,6 @@ interface IconButtonButtonProps extends HTMLButtonAttributes {
35
33
  selected?: boolean;
36
34
  loading?: boolean;
37
35
  loadingAriaLabel?: string;
38
- keepTooltipOnClick?: boolean;
39
36
  selectedIcon?: Snippet;
40
37
  shape?: 'round' | 'square';
41
38
  size?: 'xs' | 's' | 'm' | 'l' | 'xl';
@@ -49,7 +46,6 @@ interface IconButtonAnchorProps extends HTMLAnchorAttributes {
49
46
  loadingAriaLabel?: string;
50
47
  toggle?: boolean;
51
48
  selected?: boolean;
52
- keepTooltipOnClick?: boolean;
53
49
  selectedIcon?: Snippet;
54
50
  shape?: 'round' | 'square';
55
51
  size?: 'xs' | 's' | 'm' | 'l' | 'xl';
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { onMount } from 'svelte'
2
3
  import type { MenuProps } from './types.ts'
3
4
 
4
5
  let {
@@ -6,6 +7,8 @@
6
7
  element = $bindable(),
7
8
  showPopover = $bindable(),
8
9
  hidePopover = $bindable(),
10
+ open = $bindable(),
11
+ quick = false,
9
12
  style,
10
13
  popover = 'auto',
11
14
  anchor,
@@ -15,7 +18,6 @@
15
18
  let clientWidth = $state(0)
16
19
  let clientHeight = $state(0)
17
20
  let innerHeight = $state(0)
18
- let menuOpen = $state(false)
19
21
 
20
22
  showPopover = () => {
21
23
  element?.showPopover()
@@ -25,7 +27,7 @@
25
27
  element?.hidePopover()
26
28
  }
27
29
  const refreshValues = () => {
28
- if (element && anchor && menuOpen) {
30
+ if (element && anchor && open) {
29
31
  const anchorRect = anchor.getBoundingClientRect()
30
32
  let maxHeight: number
31
33
  if (innerHeight - anchorRect.bottom > anchorRect.top) {
@@ -81,10 +83,13 @@
81
83
  refreshValues()
82
84
  }
83
85
 
84
- $effect(() => {
85
- if (element && !('anchorName' in document.documentElement.style)) {
86
- getScrollableParent(element).addEventListener('scroll', onScroll, { passive: true })
86
+ const attachScrollableParent = (el: HTMLDivElement) => {
87
+ if (!('anchorName' in document.documentElement.style)) {
88
+ getScrollableParent(el).addEventListener('scroll', onScroll, { passive: true })
87
89
  }
90
+ }
91
+
92
+ onMount(() => {
88
93
  return () => {
89
94
  if (element && !('anchorName' in document.documentElement.style)) {
90
95
  getScrollableParent(element).removeEventListener('scroll', onScroll)
@@ -97,16 +102,17 @@
97
102
  <div
98
103
  role="menu"
99
104
  {...attributes}
105
+ {@attach attachScrollableParent}
100
106
  bind:this={element}
101
107
  bind:clientWidth
102
108
  bind:clientHeight
103
109
  ontoggle={(event) => {
104
110
  let { newState } = event
105
- menuOpen = newState === 'open'
111
+ open = newState === 'open'
106
112
  attributes.ontoggle?.(event)
107
113
  }}
108
114
  {popover}
109
- class={['np-menu-container', attributes.class]}
115
+ class={['np-menu-container', !quick && 'np-animate', attributes.class]}
110
116
  {style}
111
117
  >
112
118
  <div class="np-menu">
@@ -115,18 +121,7 @@
115
121
  </div>
116
122
 
117
123
  <style>
118
- .np-menu {
119
- overflow-y: auto;
120
- overflow-x: hidden;
121
- flex: 1;
122
- padding: 0.5rem 0;
123
- scrollbar-color: var(--np-color-on-surface-variant) transparent;
124
- scrollbar-width: thin;
125
- }
126
- :global(.np-menu .np-divider) {
127
- margin-block: 0.5rem;
128
- }
129
- .np-menu-container[popover] {
124
+ .np-menu-container {
130
125
  color: var(--np-menu-text-color, var(--np-color-on-surface));
131
126
  background-color: var(--np-menu-container-color, var(--np-color-surface-container));
132
127
  border: none;
@@ -135,21 +130,37 @@
135
130
  box-shadow: var(--np-elevation-2);
136
131
  margin: var(--np-menu-margin, 2px);
137
132
  inset: auto;
138
- transition:
139
- display 0.2s allow-discrete,
140
- opacity 0.2s linear;
141
- opacity: 0;
133
+ position: fixed;
134
+ bottom: anchor(bottom);
142
135
  justify-self: var(--np-menu-justify-self, anchor-center);
143
136
  position-area: var(--np-menu-position-area, bottom center);
144
137
  position-try: normal flip-block;
145
138
  z-index: 1000;
146
139
  }
147
140
 
148
- .np-menu-container:popover-open {
141
+ .np-animate[popover]:popover-open {
149
142
  opacity: 1;
150
- display: flex;
151
- @starting-style {
143
+ animation: fadeIn 0.2s linear;
144
+ }
145
+
146
+ @keyframes fadeIn {
147
+ from {
152
148
  opacity: 0;
153
149
  }
150
+ to {
151
+ opacity: 1;
152
+ }
153
+ }
154
+
155
+ .np-menu {
156
+ overflow-y: auto;
157
+ overflow-x: hidden;
158
+ flex: 1;
159
+ padding: 0.5rem 0;
160
+ scrollbar-color: var(--np-color-on-surface-variant) transparent;
161
+ scrollbar-width: thin;
162
+ }
163
+ :global(.np-menu .np-divider) {
164
+ margin-block: 0.5rem;
154
165
  }
155
166
  </style>
@@ -1,4 +1,4 @@
1
1
  import type { MenuProps } from './types.ts';
2
- declare const Menu: import("svelte").Component<MenuProps, {}, "element" | "showPopover" | "hidePopover">;
2
+ declare const Menu: import("svelte").Component<MenuProps, {}, "element" | "showPopover" | "hidePopover" | "open">;
3
3
  type Menu = ReturnType<typeof Menu>;
4
4
  export default Menu;
@@ -6,6 +6,8 @@ export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
6
6
  showPopover?: () => void;
7
7
  hidePopover?: () => void;
8
8
  element?: HTMLDivElement;
9
+ quick?: boolean;
10
+ open?: boolean;
9
11
  }
10
12
  interface ButtonProps extends HTMLButtonAttributes {
11
13
  selected?: boolean;
@@ -31,6 +31,7 @@
31
31
  selected && 'np-navigation-drawer-item-selected',
32
32
  attributes.class,
33
33
  ]}
34
+ aria-current={selected ? 'page' : undefined}
34
35
  >
35
36
  {@render content()}
36
37
  </a>
@@ -42,6 +43,8 @@
42
43
  selected && 'np-navigation-drawer-item-selected',
43
44
  attributes.class,
44
45
  ]}
46
+ type="button"
47
+ aria-pressed={selected ? 'true' : undefined}
45
48
  >
46
49
  {@render content()}
47
50
  </button>
@@ -577,9 +577,9 @@
577
577
  ? 'var(--np-outlined-select-text-field-container-shape)'
578
578
  : 'var(--np-filled-select-text-field-container-shape)'}
579
579
  anchor={anchorElement}
580
+ bind:open={menuOpen}
580
581
  ontoggle={async ({ newState }) => {
581
582
  if (newState === 'open') {
582
- menuOpen = true
583
583
  let idx = -1
584
584
  if (multiple) {
585
585
  if (Array.isArray(value) && value.length) {
@@ -594,7 +594,6 @@
594
594
  if (idx < 0) idx = 0
595
595
  focusIndex = idx
596
596
  } else {
597
- menuOpen = false
598
597
  focusIndex = -1
599
598
  }
600
599
  }}
@@ -1,20 +1,25 @@
1
1
  <script lang="ts">
2
+ import { onMount } from 'svelte'
2
3
  import type { TooltipProps } from './types.ts'
4
+ import { MediaQuery } from 'svelte/reactivity'
3
5
 
4
6
  let {
5
7
  children,
8
+ open = $bindable(),
6
9
  element = $bindable(),
7
10
  id,
8
- keepTooltipOnClick = false,
9
11
  ...attributes
10
12
  }: TooltipProps = $props()
11
13
  let clientWidth = $state(0)
12
14
  let clientHeight = $state(0)
13
15
  let innerHeight = $state(0)
14
16
  let anchor: HTMLElement | undefined = $state()
17
+ const uid = $props.id()
18
+
19
+ let isTouch = new MediaQuery('(hover: none) and (pointer: coarse)', false)
15
20
 
16
21
  const refreshValues = () => {
17
- if (element && anchor && !('positionArea' in document.documentElement.style)) {
22
+ if (element && anchor && open && !('positionArea' in document.documentElement.style)) {
18
23
  const docClientWidth = document.documentElement.clientWidth
19
24
  const anchorRect = anchor.getBoundingClientRect()
20
25
  if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
@@ -33,73 +38,64 @@
33
38
  }
34
39
  }
35
40
  $effect(refreshValues)
36
- let setAnchor = (document: Document) => {
41
+ let attachAnchor = (el: HTMLDivElement) => {
37
42
  anchor = (document.querySelector(`[aria-describedby="${id}"]`) as HTMLElement) ?? undefined
38
- }
39
-
40
- const isTouch = (event: PointerEvent) => {
41
- return event.pointerType === 'touch'
42
- }
43
-
44
- const onPointerenter = (event: PointerEvent) => {
45
- if (!isTouch(event)) {
46
- element?.showPopover()
43
+ if (!anchor) return
44
+ if ('anchorName' in document.documentElement.style) {
45
+ const anchorName = anchor.style.getPropertyValue('anchor-name')
46
+ const generatedId = anchorName || `--${uid}`
47
+ el.style.setProperty('position-anchor', generatedId)
48
+ if (!anchorName) {
49
+ anchor.style.setProperty('anchor-name', generatedId)
50
+ }
47
51
  }
52
+ anchor.addEventListener('pointerenter', showPopover)
53
+ anchor.addEventListener('pointerleave', hidePopover)
54
+ anchor.addEventListener('focus', showPopover)
55
+ anchor.addEventListener('blur', hidePopover)
48
56
  }
49
57
 
50
- const onPointerleave = (event: PointerEvent) => {
51
- if (!isTouch(event)) {
52
- element?.hidePopover()
53
- }
58
+ const showPopover = () => {
59
+ element?.showPopover()
54
60
  }
55
61
 
56
- const onPointerup = (event: PointerEvent) => {
57
- if (!isTouch(event)) {
58
- element?.hidePopover()
59
- }
62
+ const hidePopover = () => {
63
+ element?.hidePopover()
60
64
  }
61
65
 
62
- $effect(() => {
63
- if (anchor && element) {
64
- if ('anchorName' in document.documentElement.style) {
65
- const anchorName = anchor.style.getPropertyValue('anchor-name')
66
- const generatedId = anchorName || `--${crypto.randomUUID()}`
67
- element.style.setProperty('position-anchor', generatedId)
68
- if (!anchorName) {
69
- anchor.style.setProperty('anchor-name', generatedId)
70
- }
71
- }
72
- anchor.addEventListener('pointerenter', onPointerenter)
73
- anchor.addEventListener('pointerleave', onPointerleave)
74
- if (!keepTooltipOnClick) {
75
- anchor.addEventListener('pointerup', onPointerup)
76
- }
77
- }
66
+ onMount(() => {
78
67
  return () => {
79
68
  if (anchor) {
80
- anchor.removeEventListener('pointerenter', onPointerenter)
81
- anchor.removeEventListener('pointerleave', onPointerleave)
82
- anchor.removeEventListener('pointerup', onPointerup)
69
+ anchor.removeEventListener('pointerenter', showPopover)
70
+ anchor.removeEventListener('pointerleave', hidePopover)
71
+ anchor.removeEventListener('focus', showPopover)
72
+ anchor.removeEventListener('blur', hidePopover)
83
73
  }
84
74
  }
85
75
  })
86
76
  </script>
87
77
 
88
- <svelte:document use:setAnchor />
89
78
  <svelte:window bind:innerHeight />
90
79
 
91
- <div
92
- {...attributes}
93
- {id}
94
- class={['np-tooltip', attributes.class]}
95
- role="tooltip"
96
- popover="manual"
97
- bind:this={element}
98
- bind:clientWidth
99
- bind:clientHeight
100
- >
101
- {#if children}{@render children()}{/if}
102
- </div>
80
+ {#if !isTouch.current}
81
+ <div
82
+ {...attributes}
83
+ {id}
84
+ {@attach attachAnchor}
85
+ class={['np-tooltip', attributes.class]}
86
+ role="tooltip"
87
+ popover="manual"
88
+ bind:this={element}
89
+ bind:clientWidth
90
+ bind:clientHeight
91
+ ontoggle={(event) => {
92
+ let { newState } = event
93
+ open = newState === 'open'
94
+ }}
95
+ >
96
+ {#if children}{@render children()}{/if}
97
+ </div>
98
+ {/if}
103
99
 
104
100
  <style>
105
101
  .np-tooltip[popover] {
@@ -1,4 +1,4 @@
1
1
  import type { TooltipProps } from './types.ts';
2
- declare const Tooltip: import("svelte").Component<TooltipProps, {}, "element">;
2
+ declare const Tooltip: import("svelte").Component<TooltipProps, {}, "element" | "open">;
3
3
  type Tooltip = ReturnType<typeof Tooltip>;
4
4
  export default Tooltip;
@@ -1,5 +1,5 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
2
  export interface TooltipProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
3
- keepTooltipOnClick?: boolean;
4
3
  element?: HTMLDivElement;
4
+ open?: boolean;
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.24.15",
3
+ "version": "0.24.17",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {
@@ -54,26 +54,26 @@
54
54
  "svelte": "^5.32.1"
55
55
  },
56
56
  "devDependencies": {
57
- "@eslint/js": "^9.34.0",
57
+ "@eslint/js": "^9.35.0",
58
58
  "@material/material-color-utilities": "^0.3.0",
59
59
  "@playwright/test": "^1.55.0",
60
60
  "@sveltejs/adapter-vercel": "^5.10.2",
61
- "@sveltejs/kit": "^2.37.0",
61
+ "@sveltejs/kit": "^2.37.1",
62
62
  "@sveltejs/package": "^2.5.0",
63
63
  "@sveltejs/vite-plugin-svelte": "^6.1.4",
64
64
  "@types/eslint": "^9.6.1",
65
- "eslint": "^9.34.0",
65
+ "eslint": "^9.35.0",
66
66
  "eslint-config-prettier": "^10.1.8",
67
- "eslint-plugin-svelte": "^3.12.1",
67
+ "eslint-plugin-svelte": "^3.12.2",
68
68
  "globals": "^16.3.0",
69
69
  "prettier": "^3.6.2",
70
70
  "prettier-plugin-svelte": "^3.4.0",
71
71
  "publint": "^0.3.12",
72
- "svelte": "^5.38.6",
72
+ "svelte": "^5.38.7",
73
73
  "svelte-check": "^4.3.1",
74
74
  "typescript": "^5.9.2",
75
- "typescript-eslint": "^8.42.0",
76
- "vite": "^7.1.4",
75
+ "typescript-eslint": "^8.43.0",
76
+ "vite": "^7.1.5",
77
77
  "vitest": "^3.2.4"
78
78
  },
79
79
  "svelte": "./dist/index.js",