@invopop/popui 0.1.100 → 0.1.101

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,6 +20,7 @@
20
20
  let dragStartWidthPx = 0
21
21
  let dragMoved = false
22
22
  let dragDirection: 1 | -1 = 1
23
+ let activePointerId: number | null = null
23
24
 
24
25
  let isDragging = $state(false)
25
26
  let tooltipOpen = $state(false)
@@ -29,6 +30,8 @@
29
30
  const TOOLTIP_HOVER_DELAY_MS = 700
30
31
  const TOOLTIP_CURSOR_OFFSET = 12
31
32
 
33
+ let tooltipDelay = $derived(isDragging ? Number.MAX_SAFE_INTEGER : TOOLTIP_HOVER_DELAY_MS)
34
+
32
35
  let cursorAnchor = $derived.by(() => {
33
36
  const x = cursorX
34
37
  const y = cursorY
@@ -37,6 +40,9 @@
37
40
  }
38
41
  })
39
42
 
43
+ const POST_DRAG_CLICK_GUARD_MS = 250
44
+ let dragEndTime = 0
45
+
40
46
  function onPointerEnter(e: PointerEvent) {
41
47
  const sidebarRoot = (e.currentTarget as HTMLElement).closest('[data-slot="sidebar"]')
42
48
  dragDirection = sidebarRoot?.getAttribute('data-side') === 'right' ? -1 : 1
@@ -44,15 +50,33 @@
44
50
  cursorY = e.clientY
45
51
  }
46
52
 
47
- const COLLAPSE_DRAG_OVERSHOOT_PX = 100
48
- const POST_DRAG_CLICK_GUARD_MS = 250
49
- let dragEndTime = 0
53
+ function onPointerMoveOnTrigger(e: PointerEvent) {
54
+ if (activePointerId !== null) return
55
+ cursorX = e.clientX
56
+ cursorY = e.clientY
57
+ }
58
+
59
+ function endDrag() {
60
+ if (activePointerId !== null) {
61
+ window.removeEventListener('pointermove', onWindowPointerMove)
62
+ window.removeEventListener('pointerup', onWindowPointerUp)
63
+ window.removeEventListener('pointercancel', onWindowPointerUp)
64
+ activePointerId = null
65
+ }
66
+ document.body.style.cursor = ''
67
+ document.body.style.userSelect = ''
68
+ isDragging = false
69
+ sidebar.isResizing = false
70
+ if (dragMoved) {
71
+ dragEndTime = Date.now()
72
+ dragMoved = false
73
+ }
74
+ }
50
75
 
51
- function onPointerMove(e: PointerEvent) {
76
+ function onWindowPointerMove(e: PointerEvent) {
77
+ if (e.pointerId !== activePointerId) return
52
78
  cursorX = e.clientX
53
79
  cursorY = e.clientY
54
- const button = e.currentTarget as HTMLButtonElement
55
- if (!button.hasPointerCapture(e.pointerId)) return
56
80
  const delta = (e.clientX - dragStartX) * dragDirection
57
81
  if (Math.abs(delta) > SIDEBAR_DRAG_THRESHOLD_PX) {
58
82
  dragMoved = true
@@ -63,26 +87,14 @@
63
87
  if (!dragMoved) return
64
88
  if (sidebar.state === 'collapsed') {
65
89
  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()
90
+ endDrag()
73
91
  sidebar.setOpen(true)
74
92
  }
75
93
  return
76
94
  }
77
95
  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()
96
+ if (targetWidth < SIDEBAR_WIDTH_ICON_PX) {
97
+ endDrag()
86
98
  sidebar.resetWidth()
87
99
  sidebar.setOpen(false)
88
100
  return
@@ -90,35 +102,31 @@
90
102
  sidebar.setWidth(targetWidth)
91
103
  }
92
104
 
105
+ function onWindowPointerUp(e: PointerEvent) {
106
+ if (e.pointerId !== activePointerId) return
107
+ endDrag()
108
+ }
109
+
93
110
  function onPointerDown(e: PointerEvent) {
94
111
  if (sidebar.isMobile) return
95
- const button = e.currentTarget as HTMLButtonElement
96
- const sidebarRoot = button.closest('[data-slot="sidebar"]')
112
+ if (e.button !== 0) return
113
+ const target = e.currentTarget as HTMLElement
114
+ const sidebarRoot = target.closest('[data-slot="sidebar"]')
97
115
  dragDirection = sidebarRoot?.getAttribute('data-side') === 'right' ? -1 : 1
98
116
 
99
117
  const container = sidebarRoot?.querySelector('[data-slot="sidebar-container"]')
100
118
  dragStartWidthPx = container instanceof HTMLElement ? container.offsetWidth : 256
101
119
  dragStartX = e.clientX
102
120
  dragMoved = false
103
- button.setPointerCapture(e.pointerId)
121
+ activePointerId = e.pointerId
104
122
  document.body.style.cursor = 'col-resize'
105
123
  document.body.style.userSelect = 'none'
124
+ window.addEventListener('pointermove', onWindowPointerMove)
125
+ window.addEventListener('pointerup', onWindowPointerUp)
126
+ window.addEventListener('pointercancel', onWindowPointerUp)
106
127
  }
107
128
 
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
129
+ const DOUBLE_CLICK_DELAY_MS = 150
122
130
  let pendingClickTimer: ReturnType<typeof setTimeout> | undefined
123
131
 
124
132
  function onClick(e: MouseEvent) {
@@ -145,10 +153,10 @@
145
153
 
146
154
  <TooltipPrimitive.Root
147
155
  bind:open={tooltipOpen}
148
- delayDuration={TOOLTIP_HOVER_DELAY_MS}
156
+ delayDuration={tooltipDelay}
149
157
  disableHoverableContent
150
158
  >
151
- <TooltipPrimitive.Trigger disabled={isDragging}>
159
+ <TooltipPrimitive.Trigger>
152
160
  {#snippet child({ props })}
153
161
  {@const buttonProps = props as HTMLButtonAttributes}
154
162
  <button
@@ -160,17 +168,12 @@
160
168
  }}
161
169
  onpointermove={(e) => {
162
170
  buttonProps.onpointermove?.(e)
163
- onPointerMove(e)
171
+ onPointerMoveOnTrigger(e)
164
172
  }}
165
173
  onpointerdown={(e) => {
166
174
  buttonProps.onpointerdown?.(e)
167
175
  onPointerDown(e)
168
176
  }}
169
- onpointerup={(e) => {
170
- buttonProps.onpointerup?.(e)
171
- onPointerUp(e)
172
- }}
173
- onpointercancel={onPointerUp}
174
177
  onclick={(e) => {
175
178
  buttonProps.onclick?.(e)
176
179
  onClick(e)
@@ -203,19 +206,24 @@
203
206
  side="bottom"
204
207
  align="center"
205
208
  sideOffset={TOOLTIP_CURSOR_OFFSET}
206
- class="px-3 py-2"
207
209
  >
208
- <div class="flex flex-col gap-1.5">
210
+ <div class="flex flex-col gap-1">
209
211
  {#if sidebar.state === 'expanded'}
210
- <div>Drag to resize</div>
212
+ <div class="flex w-full items-center justify-between gap-3">
213
+ <span>Drag to resize</span>
214
+ <div class="flex items-center gap-0.5 opacity-0">
215
+ <ShortcutWrapper size="sm" theme="navigation">⌘</ShortcutWrapper>
216
+ <ShortcutWrapper size="sm" theme="navigation">.</ShortcutWrapper>
217
+ </div>
218
+ </div>
211
219
  {/if}
212
- <div class="flex items-center justify-between gap-3">
220
+ <div class="flex w-full items-center justify-between gap-3">
213
221
  <span>
214
222
  {sidebar.state === 'expanded' ? 'Click to collapse' : 'Click to expand'}
215
223
  </span>
216
- <div class="flex items-center gap-1">
217
- <ShortcutWrapper size="md" theme="navigation">⌘</ShortcutWrapper>
218
- <ShortcutWrapper size="md" theme="navigation">.</ShortcutWrapper>
224
+ <div class="flex items-center gap-0.5">
225
+ <ShortcutWrapper size="sm" theme="navigation">⌘</ShortcutWrapper>
226
+ <ShortcutWrapper size="sm" theme="navigation">.</ShortcutWrapper>
219
227
  </div>
220
228
  </div>
221
229
  </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.101",
5
5
  "repository": {
6
6
  "url": "https://github.com/invopop/popui"
7
7
  },