noph-ui 0.24.16 → 0.24.19

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.
@@ -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',
@@ -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>
@@ -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 {
@@ -7,7 +8,6 @@
7
8
  showPopover = $bindable(),
8
9
  hidePopover = $bindable(),
9
10
  open = $bindable(),
10
- quick = false,
11
11
  style,
12
12
  popover = 'auto',
13
13
  anchor,
@@ -82,10 +82,13 @@
82
82
  refreshValues()
83
83
  }
84
84
 
85
- $effect(() => {
86
- if (element && !('anchorName' in document.documentElement.style)) {
87
- getScrollableParent(element).addEventListener('scroll', onScroll, { passive: true })
85
+ const attachScrollableParent = (el: HTMLDivElement) => {
86
+ if (!('anchorName' in document.documentElement.style)) {
87
+ getScrollableParent(el).addEventListener('scroll', onScroll, { passive: true })
88
88
  }
89
+ }
90
+
91
+ onMount(() => {
89
92
  return () => {
90
93
  if (element && !('anchorName' in document.documentElement.style)) {
91
94
  getScrollableParent(element).removeEventListener('scroll', onScroll)
@@ -98,6 +101,7 @@
98
101
  <div
99
102
  role="menu"
100
103
  {...attributes}
104
+ {@attach attachScrollableParent}
101
105
  bind:this={element}
102
106
  bind:clientWidth
103
107
  bind:clientHeight
@@ -107,7 +111,7 @@
107
111
  attributes.ontoggle?.(event)
108
112
  }}
109
113
  {popover}
110
- class={['np-menu-container', !quick && 'np-animate', attributes.class]}
114
+ class={['np-menu-container', attributes.class]}
111
115
  {style}
112
116
  >
113
117
  <div class="np-menu">
@@ -116,7 +120,18 @@
116
120
  </div>
117
121
 
118
122
  <style>
119
- .np-menu-container {
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
+ :global(.np-menu .np-divider) {
132
+ margin-block: 0.5rem;
133
+ }
134
+ .np-menu-container[popover] {
120
135
  color: var(--np-menu-text-color, var(--np-color-on-surface));
121
136
  background-color: var(--np-menu-container-color, var(--np-color-surface-container));
122
137
  border: none;
@@ -125,35 +140,21 @@
125
140
  box-shadow: var(--np-elevation-2);
126
141
  margin: var(--np-menu-margin, 2px);
127
142
  inset: auto;
143
+ transition:
144
+ display 0.2s allow-discrete,
145
+ opacity 0.2s linear;
146
+ opacity: 0;
128
147
  justify-self: var(--np-menu-justify-self, anchor-center);
129
148
  position-area: var(--np-menu-position-area, bottom center);
130
149
  position-try: normal flip-block;
131
150
  z-index: 1000;
132
151
  }
133
152
 
134
- .np-animate[popover] {
135
- transition:
136
- opacity 0.2s ease,
137
- display 0.2s allow-discrete,
138
- overlay 0.2s allow-discrete;
139
- opacity: 0;
140
- }
141
- .np-animate[popover]:popover-open {
153
+ .np-menu-container:popover-open {
142
154
  opacity: 1;
155
+ display: flex;
143
156
  @starting-style {
144
157
  opacity: 0;
145
158
  }
146
159
  }
147
-
148
- .np-menu {
149
- overflow-y: auto;
150
- overflow-x: hidden;
151
- flex: 1;
152
- padding: 0.5rem 0;
153
- scrollbar-color: var(--np-color-on-surface-variant) transparent;
154
- scrollbar-width: thin;
155
- }
156
- :global(.np-menu .np-divider) {
157
- margin-block: 0.5rem;
158
- }
159
160
  </style>
@@ -6,7 +6,6 @@ export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
6
6
  showPopover?: () => void;
7
7
  hidePopover?: () => void;
8
8
  element?: HTMLDivElement;
9
- quick?: boolean;
10
9
  open?: boolean;
11
10
  }
12
11
  interface ButtonProps extends HTMLButtonAttributes {
@@ -1,82 +1,97 @@
1
1
  <script lang="ts">
2
- import { MediaQuery } from 'svelte/reactivity'
3
- import type { TooltipProps } from './types.ts'
4
2
  import { onMount } from 'svelte'
3
+ import type { TooltipProps } from './types.ts'
4
+ import { MediaQuery } from 'svelte/reactivity'
5
5
 
6
6
  let {
7
7
  children,
8
+ open = $bindable(),
8
9
  element = $bindable(),
9
10
  id,
10
- keepTooltipOnClick = false,
11
11
  ...attributes
12
12
  }: TooltipProps = $props()
13
+ let clientWidth = $state(0)
14
+ let clientHeight = $state(0)
15
+ let innerHeight = $state(0)
16
+ let anchor: HTMLElement | undefined = $state()
17
+ const uid = $props.id()
13
18
 
14
- const anchorNameFallback = $props.id()
15
-
16
- let touch = new MediaQuery('(pointer: coarse) and (hover: none)', false)
17
- let anchor = $state<HTMLElement>()
19
+ let isTouch = new MediaQuery('(hover: none) and (pointer: coarse)', false)
18
20
 
19
- const isTouch = (event: PointerEvent) => {
20
- return event.pointerType === 'touch'
21
+ const refreshValues = () => {
22
+ if (element && anchor && open && !('positionArea' in document.documentElement.style)) {
23
+ const docClientWidth = document.documentElement.clientWidth
24
+ const anchorRect = anchor.getBoundingClientRect()
25
+ if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
26
+ element.style.top = `${anchorRect.top - clientHeight - 8}px`
27
+ } else {
28
+ element.style.top = `${anchorRect.bottom}px`
29
+ }
30
+ const left = anchorRect.left + anchorRect.width / 2 - clientWidth / 2
31
+ if (left < 2) {
32
+ element.style.left = '2px'
33
+ } else if (left > docClientWidth - clientWidth) {
34
+ element.style.left = `${docClientWidth - clientWidth - 2}px`
35
+ } else {
36
+ element.style.left = `${anchorRect.left + anchorRect.width / 2 - clientWidth / 2}px`
37
+ }
38
+ }
21
39
  }
22
-
23
- const onPointerenter = (event: PointerEvent) => {
24
- if (!isTouch(event)) {
25
- element?.showPopover()
40
+ $effect(refreshValues)
41
+ let attachAnchor = (el: HTMLDivElement) => {
42
+ anchor = (document.querySelector(`[aria-describedby="${id}"]`) as HTMLElement) ?? undefined
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
+ }
26
51
  }
52
+ anchor.addEventListener('pointerenter', showPopover)
53
+ anchor.addEventListener('pointerleave', hidePopover)
54
+ anchor.addEventListener('focus', showPopover)
55
+ anchor.addEventListener('blur', hidePopover)
27
56
  }
28
57
 
29
- const onPointerleave = (event: PointerEvent) => {
30
- if (!isTouch(event)) {
31
- element?.hidePopover()
32
- }
58
+ const showPopover = () => {
59
+ element?.showPopover()
33
60
  }
34
61
 
35
- const onPointerup = (event: PointerEvent) => {
36
- if (!isTouch(event)) {
37
- element?.hidePopover()
38
- }
62
+ const hidePopover = () => {
63
+ element?.hidePopover()
39
64
  }
65
+
40
66
  onMount(() => {
41
67
  return () => {
42
68
  if (anchor) {
43
- anchor.style.removeProperty('anchor-name')
44
- anchor.removeEventListener('pointerenter', onPointerenter)
45
- anchor.removeEventListener('pointerleave', onPointerleave)
46
- anchor.removeEventListener('pointerup', onPointerup)
69
+ anchor.removeEventListener('pointerenter', showPopover)
70
+ anchor.removeEventListener('pointerleave', hidePopover)
71
+ anchor.removeEventListener('focus', showPopover)
72
+ anchor.removeEventListener('blur', hidePopover)
47
73
  }
48
74
  }
49
75
  })
50
76
  </script>
51
77
 
52
- {#if !touch.current}
78
+ <svelte:window bind:innerHeight />
79
+
80
+ {#if !isTouch.current}
53
81
  <div
54
82
  {...attributes}
55
- {@attach (element) => {
56
- if (id && 'positionArea' in document.documentElement.style) {
57
- anchor = (document.querySelector(`[aria-describedby="${id}"]`) as HTMLElement) ?? undefined
58
-
59
- if ('anchorName' in document.documentElement.style) {
60
- const anchorName = anchor.style.getPropertyValue('anchor-name')
61
- const generatedId = anchorName || `--${anchorNameFallback}`
62
- element.style.setProperty('position-anchor', generatedId)
63
- if (!anchorName) {
64
- anchor.style.setProperty('anchor-name', generatedId)
65
- }
66
- }
67
- anchor.addEventListener('pointerenter', onPointerenter)
68
- anchor.addEventListener('pointerleave', onPointerleave)
69
- if (!keepTooltipOnClick) {
70
- anchor.addEventListener('pointerup', onPointerup)
71
- }
72
- }
73
- }}
74
83
  {id}
84
+ {@attach attachAnchor}
75
85
  class={['np-tooltip', attributes.class]}
76
86
  role="tooltip"
77
- aria-live="polite"
78
87
  popover="manual"
79
88
  bind:this={element}
89
+ bind:clientWidth
90
+ bind:clientHeight
91
+ ontoggle={(event) => {
92
+ let { newState } = event
93
+ open = newState === 'open'
94
+ }}
80
95
  >
81
96
  {#if children}{@render children()}{/if}
82
97
  </div>
@@ -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.16",
3
+ "version": "0.24.19",
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",