noph-ui 0.9.2 → 0.9.4

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,9 +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: 0;
120
- margin-bottom: 2px;
121
- margin-top: 2px;
110
+ margin: var(--np-menu-margin, 2px);
122
111
  max-height: 80dvh;
123
112
  scrollbar-color: var(--np-color-on-surface-variant) transparent;
124
113
  scrollbar-width: thin;
@@ -126,19 +115,9 @@
126
115
  display 0.2s allow-discrete,
127
116
  opacity 0.2s linear;
128
117
  opacity: 0;
129
- z-index: 1;
130
- }
131
- .bottom-left.np-menu[popover] {
132
- top: anchor(bottom);
133
- left: anchor(left);
134
- justify-self: anchor-center;
135
- position-try-fallbacks: --menu-top-left;
136
- }
137
-
138
- .bottom.np-menu[popover] {
139
- top: anchor(bottom);
140
- position-try-fallbacks: --menu-top;
141
- 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;
142
121
  }
143
122
 
144
123
  .np-menu:popover-open {
@@ -147,13 +126,7 @@
147
126
  opacity: 0;
148
127
  }
149
128
  }
150
- @position-try --menu-top {
151
- inset: auto;
152
- bottom: anchor(top);
153
- }
154
- @position-try --menu-top-left {
155
- inset: auto;
156
- bottom: anchor(top);
157
- left: anchor(left);
129
+ @position-try --np-menu-position-fallback {
130
+ position-area: var(--np-menu-position-area-fallback, top center);
158
131
  }
159
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.ts'
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.2",
3
+ "version": "0.9.4",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {