noph-ui 0.24.16 → 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.
@@ -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 {
@@ -82,10 +83,13 @@
82
83
  refreshValues()
83
84
  }
84
85
 
85
- $effect(() => {
86
- if (element && !('anchorName' in document.documentElement.style)) {
87
- 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 })
88
89
  }
90
+ }
91
+
92
+ onMount(() => {
89
93
  return () => {
90
94
  if (element && !('anchorName' in document.documentElement.style)) {
91
95
  getScrollableParent(element).removeEventListener('scroll', onScroll)
@@ -98,6 +102,7 @@
98
102
  <div
99
103
  role="menu"
100
104
  {...attributes}
105
+ {@attach attachScrollableParent}
101
106
  bind:this={element}
102
107
  bind:clientWidth
103
108
  bind:clientHeight
@@ -125,24 +130,26 @@
125
130
  box-shadow: var(--np-elevation-2);
126
131
  margin: var(--np-menu-margin, 2px);
127
132
  inset: auto;
133
+ position: fixed;
134
+ bottom: anchor(bottom);
128
135
  justify-self: var(--np-menu-justify-self, anchor-center);
129
136
  position-area: var(--np-menu-position-area, bottom center);
130
137
  position-try: normal flip-block;
131
138
  z-index: 1000;
132
139
  }
133
140
 
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
141
  .np-animate[popover]:popover-open {
142
142
  opacity: 1;
143
- @starting-style {
143
+ animation: fadeIn 0.2s linear;
144
+ }
145
+
146
+ @keyframes fadeIn {
147
+ from {
144
148
  opacity: 0;
145
149
  }
150
+ to {
151
+ opacity: 1;
152
+ }
146
153
  }
147
154
 
148
155
  .np-menu {
@@ -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.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",