noph-ui 0.9.3 → 0.9.5

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.
@@ -8,7 +8,6 @@
8
8
  element = $bindable(),
9
9
  showPopover = $bindable(),
10
10
  hidePopover = $bindable(),
11
- position = 'bottom',
12
11
  ...attributes
13
12
  }: MenuProps = $props()
14
13
 
@@ -16,9 +15,6 @@
16
15
  let clientHeight = $state(0)
17
16
  let innerHeight = $state(0)
18
17
  let innerWidth = $state(0)
19
- let scrollY = $state(0)
20
- let scrollX = $state(0)
21
- let anchorId = anchor?.style.getPropertyValue('anchor-name')
22
18
 
23
19
  showPopover = () => {
24
20
  element?.showPopover()
@@ -30,6 +26,7 @@
30
26
 
31
27
  const refreshValues = () => {
32
28
  if (element && anchor && !('anchorName' in document.documentElement.style)) {
29
+ const docClientWidth = document.documentElement.clientWidth
33
30
  const anchorRect = anchor.getBoundingClientRect()
34
31
  if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
35
32
  element.style.top = `${anchorRect.top - clientHeight - 2}px`
@@ -37,10 +34,10 @@
37
34
  element.style.top = `${anchorRect.bottom + 2}px`
38
35
  }
39
36
  const left = anchorRect.left + anchorRect.width / 2 - clientWidth / 2
40
- if (left > innerWidth - clientWidth) {
41
- element.style.left = `${innerWidth - clientWidth - 8}px`
42
- } else if (left < 8) {
43
- element.style.left = '8px'
37
+ if (left < 2) {
38
+ element.style.left = '2px'
39
+ } else if (left > docClientWidth - clientWidth) {
40
+ element.style.left = `${docClientWidth - clientWidth - 4}px`
44
41
  } else {
45
42
  element.style.left = `${anchorRect.left + anchorRect.width / 2 - clientWidth / 2}px`
46
43
  }
@@ -78,7 +75,7 @@
78
75
  },
79
76
  { passive: true },
80
77
  )
81
- } else if (!anchorId) {
78
+ } else if (!anchor.style.getPropertyValue('anchor-name')) {
82
79
  const generatedId = `--${generateUUIDv4()}`
83
80
  element.style.setProperty('position-anchor', generatedId)
84
81
  anchor.style.setProperty('anchor-name', generatedId)
@@ -87,13 +84,7 @@
87
84
  })
88
85
  </script>
89
86
 
90
- <svelte:window
91
- bind:scrollX
92
- bind:scrollY
93
- bind:innerHeight
94
- bind:innerWidth
95
- onresize={refreshValues}
96
- />
87
+ <svelte:window bind:innerHeight onresize={refreshValues} />
97
88
 
98
89
  <div
99
90
  {...attributes}
@@ -101,7 +92,7 @@
101
92
  bind:clientWidth
102
93
  bind:clientHeight
103
94
  popover="auto"
104
- class={[position, 'np-menu', attributes.class]}
95
+ class={['np-menu', attributes.class]}
105
96
  role="menu"
106
97
  >
107
98
  {@render children()}
@@ -116,7 +107,7 @@
116
107
  border-radius: var(--np-menu-container-shape, var(--np-shape-corner-extra-small));
117
108
  padding: 0.5rem 0;
118
109
  box-shadow: var(--np-elevation-2);
119
- margin: 2px;
110
+ margin: var(--np-menu-margin, 2px);
120
111
  max-height: 80dvh;
121
112
  scrollbar-color: var(--np-color-on-surface-variant) transparent;
122
113
  scrollbar-width: thin;
@@ -124,19 +115,9 @@
124
115
  display 0.2s allow-discrete,
125
116
  opacity 0.2s linear;
126
117
  opacity: 0;
127
- z-index: 1;
128
- }
129
- .bottom-left.np-menu[popover] {
130
- top: anchor(bottom);
131
- left: anchor(left);
132
- justify-self: anchor-center;
133
- position-try-fallbacks: --menu-top-left;
134
- }
135
-
136
- .bottom.np-menu[popover] {
137
- top: anchor(bottom);
138
- position-try-fallbacks: --menu-top;
139
- justify-self: anchor-center;
118
+ justify-self: var(--np-menu-justify-self, anchor-center);
119
+ position-area: var(--np-menu-position-area, bottom center);
120
+ position-try-fallbacks: --np-menu-position-fallback;
140
121
  }
141
122
 
142
123
  .np-menu:popover-open {
@@ -145,13 +126,7 @@
145
126
  opacity: 0;
146
127
  }
147
128
  }
148
- @position-try --menu-top {
149
- inset: auto;
150
- bottom: anchor(top);
151
- }
152
- @position-try --menu-top-left {
153
- inset: auto;
154
- bottom: anchor(top);
155
- left: anchor(left);
129
+ @position-try --np-menu-position-fallback {
130
+ position-area: var(--np-menu-position-area-fallback, top center);
156
131
  }
157
132
  </style>
@@ -5,7 +5,6 @@ export interface MenuProps extends HTMLAttributes<HTMLDivElement> {
5
5
  anchor?: HTMLElement | undefined;
6
6
  showPopover?: () => void;
7
7
  hidePopover?: () => void;
8
- position?: 'bottom-left' | 'bottom';
9
8
  element?: HTMLDivElement;
10
9
  }
11
10
  interface ButtonProps extends HTMLButtonAttributes {
@@ -197,9 +197,12 @@
197
197
  </div>
198
198
 
199
199
  <Menu
200
- style="position-anchor:{menuId};min-width: {clientWidth}px;"
200
+ style="position-anchor:{menuId};min-width: 300px;"
201
201
  popover="manual"
202
- position="bottom-left"
202
+ --np-menu-justify-self="none"
203
+ --np-menu-position-area-fallback="top span-right"
204
+ --np-menu-position-area="bottom span-right"
205
+ --np-menu-margin="2px 0"
203
206
  anchor={element}
204
207
  ontoggle={({ newState }) => {
205
208
  if (newState === 'open') {
@@ -1,51 +1,54 @@
1
1
  <script lang="ts">
2
+ import { generateUUIDv4 } from '../utils.js'
2
3
  import type { TooltipProps } from './types.ts'
3
4
 
4
5
  let { children, element = $bindable(), id, ...attributes }: TooltipProps = $props()
5
6
  let clientWidth = $state(0)
6
7
  let clientHeight = $state(0)
7
8
  let innerHeight = $state(0)
8
- let innerWidth = $state(0)
9
9
  let anchor: HTMLElement | undefined = $state()
10
- let anchorRect: DOMRect | undefined = $state()
11
10
 
12
- const distanceToBorder = 8
13
-
14
- let calculateLeftPos = $derived.by(() => {
15
- if (!anchor || !anchorRect) {
16
- return 0
17
- }
18
- const left = anchorRect.left + anchorRect.width / 2 - clientWidth / 2
19
- if (left < distanceToBorder) {
20
- return distanceToBorder
21
- }
22
- if (innerWidth < left + clientWidth + distanceToBorder) {
23
- return innerWidth - clientWidth - distanceToBorder
24
- }
25
- return anchorRect.left + anchorRect.width / 2 - clientWidth / 2
26
- })
27
-
28
- let calculateTopPos = $derived.by(() => {
29
- if (!anchorRect) {
30
- return 0
11
+ const refreshValues = () => {
12
+ if (element && anchor && !('anchorName' in document.documentElement.style)) {
13
+ const docClientWidth = document.documentElement.clientWidth
14
+ const anchorRect = anchor.getBoundingClientRect()
15
+ if (anchorRect.bottom + clientHeight > innerHeight && anchorRect.top - clientHeight > 0) {
16
+ element.style.top = `${anchorRect.top - clientHeight - 8}px`
17
+ } else {
18
+ element.style.top = `${anchorRect.bottom}px`
19
+ }
20
+ const left = anchorRect.left + anchorRect.width / 2 - clientWidth / 2
21
+ if (left < 2) {
22
+ element.style.left = '2px'
23
+ } else if (left > docClientWidth - clientWidth) {
24
+ element.style.left = `${docClientWidth - clientWidth - 2}px`
25
+ } else {
26
+ element.style.left = `${anchorRect.left + anchorRect.width / 2 - clientWidth / 2}px`
27
+ }
31
28
  }
32
- const top = anchorRect.bottom + 4
33
- if (top + clientHeight > innerHeight) {
34
- return anchorRect.top - clientHeight - 4
35
- }
36
- return top
37
- })
29
+ }
30
+ $effect(refreshValues)
38
31
  let setAnchor = (document: Document) => {
39
32
  anchor = (document.querySelector(`[aria-describedby="${id}"]`) as HTMLElement) ?? undefined
40
33
  }
41
34
 
42
35
  $effect(() => {
43
- if (anchor) {
44
- anchor.addEventListener('pointerenter', () => {
45
- anchorRect = anchor?.getBoundingClientRect() ?? undefined
36
+ if (anchor && element) {
37
+ if ('anchorName' in document.documentElement.style) {
38
+ const anchorName = anchor.style.getPropertyValue('anchor-name')
39
+ const generatedId = anchorName || `--${generateUUIDv4()}`
40
+ element.style.setProperty('position-anchor', generatedId)
41
+ if (!anchorName) {
42
+ anchor.style.setProperty('anchor-name', generatedId)
43
+ }
44
+ }
45
+ anchor.addEventListener('mouseenter', () => {
46
46
  element?.showPopover()
47
47
  })
48
- anchor.addEventListener('pointerleave', () => {
48
+ anchor.addEventListener('mouseleave', () => {
49
+ element?.hidePopover()
50
+ })
51
+ anchor.addEventListener('click', () => {
49
52
  element?.hidePopover()
50
53
  })
51
54
  }
@@ -53,13 +56,12 @@
53
56
  </script>
54
57
 
55
58
  <svelte:document use:setAnchor />
56
- <svelte:window bind:innerHeight bind:innerWidth />
59
+ <svelte:window bind:innerHeight />
57
60
 
58
61
  <div
59
62
  {...attributes}
60
63
  {id}
61
64
  class={['np-tooltip', attributes.class]}
62
- style="top:{calculateTopPos}px;left:{calculateLeftPos}px;{attributes.style}"
63
65
  role="tooltip"
64
66
  popover="manual"
65
67
  bind:this={element}
@@ -70,25 +72,31 @@
70
72
  </div>
71
73
 
72
74
  <style>
73
- .np-tooltip {
75
+ .np-tooltip[popover] {
74
76
  width: max-content;
75
- margin: 0;
77
+ margin: 4px 0;
76
78
  background: var(--np-color-inverse-surface);
77
79
  color: var(--np-color-inverse-on-surface);
78
80
  padding: 0.25rem 0.5rem;
81
+ border: none;
79
82
  border-radius: 0.25rem;
80
83
  line-height: 1rem;
81
84
  font-size: 0.75rem;
82
85
  opacity: 0;
83
86
  transition:
84
- overlay 0.3s allow-discrete,
85
87
  display 0.3s allow-discrete,
86
88
  opacity 0.3s ease;
89
+ justify-self: var(--np-tooltip-justify-self, anchor-center);
90
+ position-area: var(--np-tooltip-position-area, top);
91
+ position-try-fallbacks: --np-tooltip-position-fallback;
87
92
  }
88
93
  .np-tooltip:popover-open {
89
94
  opacity: 1;
90
95
  animation: scaleIn 0.3s ease;
91
96
  }
97
+ @position-try --np-tooltip-position-fallback {
98
+ position-area: var(--np-tooltip-position-area-fallback, bottom);
99
+ }
92
100
 
93
101
  @keyframes scaleIn {
94
102
  from {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {