@invopop/popui 0.1.100 → 0.1.102

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.
@@ -3,8 +3,9 @@ export declare const SIDEBAR_WIDTH_STORAGE_KEY = "sidebar_width";
3
3
  export declare const SIDEBAR_COOKIE_MAX_AGE: number;
4
4
  export declare const SIDEBAR_WIDTH = "16rem";
5
5
  export declare const SIDEBAR_WIDTH_MOBILE = "18rem";
6
- export declare const SIDEBAR_WIDTH_ICON = "3rem";
6
+ export declare const SIDEBAR_WIDTH_ICON = "3.5rem";
7
+ export declare const SIDEBAR_WIDTH_ICON_PX = 56;
7
8
  export declare const SIDEBAR_KEYBOARD_SHORTCUT = ".";
8
- export declare const SIDEBAR_MIN_WIDTH_PX = 240;
9
+ export declare const SIDEBAR_MIN_WIDTH_PX = 180;
9
10
  export declare const SIDEBAR_MAX_WIDTH_PX = 384;
10
11
  export declare const SIDEBAR_DRAG_THRESHOLD_PX = 4;
@@ -3,8 +3,9 @@ export const SIDEBAR_WIDTH_STORAGE_KEY = 'sidebar_width';
3
3
  export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
4
4
  export const SIDEBAR_WIDTH = '16rem';
5
5
  export const SIDEBAR_WIDTH_MOBILE = '18rem';
6
- export const SIDEBAR_WIDTH_ICON = '3rem';
6
+ export const SIDEBAR_WIDTH_ICON = '3.5rem';
7
+ export const SIDEBAR_WIDTH_ICON_PX = 56;
7
8
  export const SIDEBAR_KEYBOARD_SHORTCUT = '.';
8
- export const SIDEBAR_MIN_WIDTH_PX = 240;
9
+ export const SIDEBAR_MIN_WIDTH_PX = 180;
9
10
  export const SIDEBAR_MAX_WIDTH_PX = 384;
10
11
  export const SIDEBAR_DRAG_THRESHOLD_PX = 4;
@@ -4,7 +4,7 @@
4
4
  import ShortcutWrapper from '../ShortcutWrapper.svelte'
5
5
  import TooltipContent from '../tooltip/tooltip-content.svelte'
6
6
  import { cn, type WithElementRef } from '../utils.js'
7
- import { SIDEBAR_DRAG_THRESHOLD_PX, SIDEBAR_MIN_WIDTH_PX } from './constants.js'
7
+ import { SIDEBAR_DRAG_THRESHOLD_PX, SIDEBAR_WIDTH_ICON_PX } from './constants.js'
8
8
  import { useSidebar } from './context.svelte.js'
9
9
 
10
10
  let {
@@ -20,8 +20,11 @@
20
20
  let dragStartWidthPx = 0
21
21
  let dragMoved = false
22
22
  let dragDirection: 1 | -1 = 1
23
+ let activePointerId: number | null = null
24
+ let resizeCommitted = false
23
25
 
24
26
  let isDragging = $state(false)
27
+ let isPointerDown = $state(false)
25
28
  let tooltipOpen = $state(false)
26
29
  let cursorX = $state(0)
27
30
  let cursorY = $state(0)
@@ -37,6 +40,30 @@
37
40
  }
38
41
  })
39
42
 
43
+ $effect(() => {
44
+ if ((isDragging || isPointerDown) && tooltipOpen) {
45
+ tooltipOpen = false
46
+ }
47
+ })
48
+
49
+ let globalDragStyleEl: HTMLStyleElement | null = null
50
+
51
+ function setGlobalDragStyles() {
52
+ if (globalDragStyleEl) return
53
+ globalDragStyleEl = document.createElement('style')
54
+ globalDragStyleEl.textContent =
55
+ '*, *::before, *::after { cursor: col-resize !important; user-select: none !important; }'
56
+ document.head.appendChild(globalDragStyleEl)
57
+ }
58
+
59
+ function clearGlobalDragStyles() {
60
+ globalDragStyleEl?.remove()
61
+ globalDragStyleEl = null
62
+ }
63
+
64
+ const POST_DRAG_CLICK_GUARD_MS = 250
65
+ let dragEndTime = 0
66
+
40
67
  function onPointerEnter(e: PointerEvent) {
41
68
  const sidebarRoot = (e.currentTarget as HTMLElement).closest('[data-slot="sidebar"]')
42
69
  dragDirection = sidebarRoot?.getAttribute('data-side') === 'right' ? -1 : 1
@@ -44,15 +71,35 @@
44
71
  cursorY = e.clientY
45
72
  }
46
73
 
47
- const COLLAPSE_DRAG_OVERSHOOT_PX = 100
48
- const POST_DRAG_CLICK_GUARD_MS = 250
49
- let dragEndTime = 0
74
+ function onPointerMoveOnTrigger(e: PointerEvent) {
75
+ if (activePointerId !== null) return
76
+ cursorX = e.clientX
77
+ cursorY = e.clientY
78
+ }
79
+
80
+ function endDrag() {
81
+ if (activePointerId !== null) {
82
+ window.removeEventListener('pointermove', onWindowPointerMove)
83
+ window.removeEventListener('pointerup', onWindowPointerUp)
84
+ window.removeEventListener('pointercancel', onWindowPointerUp)
85
+ activePointerId = null
86
+ }
87
+ clearGlobalDragStyles()
88
+ isDragging = false
89
+ isPointerDown = false
90
+ sidebar.isResizing = false
91
+ resizeCommitted = false
92
+ if (dragMoved) {
93
+ dragEndTime = Date.now()
94
+ dragMoved = false
95
+ }
96
+ }
50
97
 
51
- function onPointerMove(e: PointerEvent) {
98
+ function onWindowPointerMove(e: PointerEvent) {
99
+ if (e.pointerId !== activePointerId) return
52
100
  cursorX = e.clientX
53
101
  cursorY = e.clientY
54
- const button = e.currentTarget as HTMLButtonElement
55
- if (!button.hasPointerCapture(e.pointerId)) return
102
+ if (resizeCommitted) return
56
103
  const delta = (e.clientX - dragStartX) * dragDirection
57
104
  if (Math.abs(delta) > SIDEBAR_DRAG_THRESHOLD_PX) {
58
105
  dragMoved = true
@@ -63,26 +110,14 @@
63
110
  if (!dragMoved) return
64
111
  if (sidebar.state === 'collapsed') {
65
112
  if (delta > 0) {
66
- button.releasePointerCapture(e.pointerId)
67
- document.body.style.cursor = ''
68
- document.body.style.userSelect = ''
69
- isDragging = false
70
- sidebar.isResizing = false
71
- dragMoved = false
72
- dragEndTime = Date.now()
113
+ resizeCommitted = true
73
114
  sidebar.setOpen(true)
74
115
  }
75
116
  return
76
117
  }
77
118
  const targetWidth = dragStartWidthPx + delta
78
- if (targetWidth < SIDEBAR_MIN_WIDTH_PX - COLLAPSE_DRAG_OVERSHOOT_PX) {
79
- button.releasePointerCapture(e.pointerId)
80
- document.body.style.cursor = ''
81
- document.body.style.userSelect = ''
82
- isDragging = false
83
- sidebar.isResizing = false
84
- dragMoved = false
85
- dragEndTime = Date.now()
119
+ if (targetWidth < SIDEBAR_WIDTH_ICON_PX) {
120
+ resizeCommitted = true
86
121
  sidebar.resetWidth()
87
122
  sidebar.setOpen(false)
88
123
  return
@@ -90,35 +125,32 @@
90
125
  sidebar.setWidth(targetWidth)
91
126
  }
92
127
 
128
+ function onWindowPointerUp(e: PointerEvent) {
129
+ if (e.pointerId !== activePointerId) return
130
+ endDrag()
131
+ }
132
+
93
133
  function onPointerDown(e: PointerEvent) {
94
134
  if (sidebar.isMobile) return
95
- const button = e.currentTarget as HTMLButtonElement
96
- const sidebarRoot = button.closest('[data-slot="sidebar"]')
135
+ if (e.button !== 0) return
136
+ const target = e.currentTarget as HTMLElement
137
+ const sidebarRoot = target.closest('[data-slot="sidebar"]')
97
138
  dragDirection = sidebarRoot?.getAttribute('data-side') === 'right' ? -1 : 1
98
139
 
99
140
  const container = sidebarRoot?.querySelector('[data-slot="sidebar-container"]')
100
141
  dragStartWidthPx = container instanceof HTMLElement ? container.offsetWidth : 256
101
142
  dragStartX = e.clientX
102
143
  dragMoved = false
103
- button.setPointerCapture(e.pointerId)
104
- document.body.style.cursor = 'col-resize'
105
- document.body.style.userSelect = 'none'
144
+ activePointerId = e.pointerId
145
+ isPointerDown = true
146
+ tooltipOpen = false
147
+ setGlobalDragStyles()
148
+ window.addEventListener('pointermove', onWindowPointerMove)
149
+ window.addEventListener('pointerup', onWindowPointerUp)
150
+ window.addEventListener('pointercancel', onWindowPointerUp)
106
151
  }
107
152
 
108
- function onPointerUp(e: PointerEvent) {
109
- const button = e.currentTarget as HTMLButtonElement
110
- if (button.hasPointerCapture(e.pointerId)) button.releasePointerCapture(e.pointerId)
111
- document.body.style.cursor = ''
112
- document.body.style.userSelect = ''
113
- isDragging = false
114
- sidebar.isResizing = false
115
- if (dragMoved) {
116
- dragEndTime = Date.now()
117
- dragMoved = false
118
- }
119
- }
120
-
121
- const DOUBLE_CLICK_DELAY_MS = 300
153
+ const DOUBLE_CLICK_DELAY_MS = 150
122
154
  let pendingClickTimer: ReturnType<typeof setTimeout> | undefined
123
155
 
124
156
  function onClick(e: MouseEvent) {
@@ -148,7 +180,7 @@
148
180
  delayDuration={TOOLTIP_HOVER_DELAY_MS}
149
181
  disableHoverableContent
150
182
  >
151
- <TooltipPrimitive.Trigger disabled={isDragging}>
183
+ <TooltipPrimitive.Trigger>
152
184
  {#snippet child({ props })}
153
185
  {@const buttonProps = props as HTMLButtonAttributes}
154
186
  <button
@@ -160,17 +192,12 @@
160
192
  }}
161
193
  onpointermove={(e) => {
162
194
  buttonProps.onpointermove?.(e)
163
- onPointerMove(e)
195
+ onPointerMoveOnTrigger(e)
164
196
  }}
165
197
  onpointerdown={(e) => {
166
198
  buttonProps.onpointerdown?.(e)
167
199
  onPointerDown(e)
168
200
  }}
169
- onpointerup={(e) => {
170
- buttonProps.onpointerup?.(e)
171
- onPointerUp(e)
172
- }}
173
- onpointercancel={onPointerUp}
174
201
  onclick={(e) => {
175
202
  buttonProps.onclick?.(e)
176
203
  onClick(e)
@@ -203,19 +230,24 @@
203
230
  side="bottom"
204
231
  align="center"
205
232
  sideOffset={TOOLTIP_CURSOR_OFFSET}
206
- class="px-3 py-2"
207
233
  >
208
- <div class="flex flex-col gap-1.5">
234
+ <div class="flex flex-col gap-1">
209
235
  {#if sidebar.state === 'expanded'}
210
- <div>Drag to resize</div>
236
+ <div class="flex w-full items-center justify-between gap-3">
237
+ <span>Drag to resize</span>
238
+ <div class="flex items-center gap-0.5 opacity-0">
239
+ <ShortcutWrapper size="sm" theme="navigation">⌘</ShortcutWrapper>
240
+ <ShortcutWrapper size="sm" theme="navigation">.</ShortcutWrapper>
241
+ </div>
242
+ </div>
211
243
  {/if}
212
- <div class="flex items-center justify-between gap-3">
244
+ <div class="flex w-full items-center justify-between gap-3">
213
245
  <span>
214
246
  {sidebar.state === 'expanded' ? 'Click to collapse' : 'Click to expand'}
215
247
  </span>
216
- <div class="flex items-center gap-1">
217
- <ShortcutWrapper size="md" theme="navigation">⌘</ShortcutWrapper>
218
- <ShortcutWrapper size="md" theme="navigation">.</ShortcutWrapper>
248
+ <div class="flex items-center gap-0.5">
249
+ <ShortcutWrapper size="sm" theme="navigation">⌘</ShortcutWrapper>
250
+ <ShortcutWrapper size="sm" theme="navigation">.</ShortcutWrapper>
219
251
  </div>
220
252
  </div>
221
253
  </div>
@@ -73,7 +73,7 @@
73
73
  <div
74
74
  data-slot="sidebar-gap"
75
75
  class={cn(
76
- 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear group-data-[resizing=true]:transition-none',
76
+ 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-150 ease-linear group-data-[resizing=true]:transition-none',
77
77
  'group-data-[collapsible=offcanvas]:w-0',
78
78
  'group-data-[side=right]:rotate-180',
79
79
  variant === 'floating' || variant === 'inset'
@@ -84,7 +84,7 @@
84
84
  <div
85
85
  data-slot="sidebar-container"
86
86
  class={cn(
87
- 'fixed inset-y-0 z-50 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear group-data-[resizing=true]:transition-none md:flex',
87
+ 'fixed inset-y-0 z-50 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-150 ease-linear group-data-[resizing=true]:transition-none md:flex',
88
88
  side === 'left'
89
89
  ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
90
90
  : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
@@ -24,7 +24,7 @@
24
24
  {side}
25
25
  {...rest}
26
26
  class={cn(
27
- 'bg-background-default-negative border border-border-inverse z-[1002] rounded-md px-2 py-1 text-sm font-medium text-foreground-inverse leading-5 tracking-tight shadow-md',
27
+ 'bg-background-default-negative border border-border-inverse z-1002 rounded-md px-2 py-1 text-base font-medium text-foreground-inverse shadow-md',
28
28
  className
29
29
  )}
30
30
  >
@@ -34,11 +34,11 @@
34
34
  {#snippet child({ props })}
35
35
  <div
36
36
  class={cn(
37
- 'bg-background-default-negative z-[1002] size-2.5 rotate-45 rounded-[2px]',
38
- 'data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]',
39
- 'data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%_+_1px)]',
40
- 'data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2',
41
- 'data-[side=left]:-translate-y-[calc(50%_-_3px)]',
37
+ 'bg-background-default-negative z-1002 size-2.5 rotate-45 rounded-xs',
38
+ 'data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%+2px)]',
39
+ 'data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%+1px)]',
40
+ 'data-[side=right]:translate-x-[calc(50%+2px)] data-[side=right]:translate-y-1/2',
41
+ 'data-[side=left]:-translate-y-[calc(50%-3px)]',
42
42
  arrowClasses
43
43
  )}
44
44
  {...props}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@invopop/popui",
3
3
  "license": "MIT",
4
- "version": "0.1.100",
4
+ "version": "0.1.102",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },