@a4ui/core 0.31.0 → 0.31.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a4ui/core",
3
- "version": "0.31.0",
3
+ "version": "0.31.1",
4
4
  "description": "A4ui — Spatial Glass design system & component library for SolidJS (Kobalte behavior + Tailwind glass tokens + motion).",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  // import '@a4ui/core/styles.css'
9
9
  // import { Button, Card, Modal } from '@a4ui/core'
10
10
 
11
- export const A4UI_VERSION = '0.31.0'
11
+ export const A4UI_VERSION = '0.31.1'
12
12
 
13
13
  // Helpers (src/lib) — generic, framework-level utilities.
14
14
  export { cn } from './lib/cn'
@@ -176,21 +176,27 @@ export function CardSpread(props: CardSpreadProps): JSX.Element {
176
176
  <div
177
177
  role="group"
178
178
  aria-label={props['aria-label']}
179
- class={cn('relative inline-block h-56 w-40', props.class)}
179
+ class={cn('relative mx-auto h-96 w-full max-w-lg', props.class)}
180
180
  onPointerEnter={handleEnter}
181
181
  onPointerLeave={handleLeave}
182
182
  >
183
- <For each={props.items}>
184
- {(item, i) => (
185
- <div
186
- ref={(el) => (cardRefs[i()] = el)}
187
- class="absolute inset-x-0 top-0 h-56 w-40 rounded-xl border border-border bg-card text-card-foreground shadow-sm will-change-transform"
188
- style={{ transform: transformString(restTransform(i(), props.items.length)) }}
189
- >
190
- {item}
191
- </div>
192
- )}
193
- </For>
183
+ {/* The cards fan around this fixed, centered anchor while the wider root
184
+ above stays the (stable) hover zone: a fanned spread reaches far past
185
+ a single card's box, so anchoring the hit-area to one card made the
186
+ cards leave it and flicker enter/leave. */}
187
+ <div class="absolute left-1/2 top-1/2 h-56 w-40 -translate-x-1/2 -translate-y-1/2">
188
+ <For each={props.items}>
189
+ {(item, i) => (
190
+ <div
191
+ ref={(el) => (cardRefs[i()] = el)}
192
+ class="absolute inset-0 rounded-xl border border-border bg-card text-card-foreground shadow-sm will-change-transform"
193
+ style={{ transform: transformString(restTransform(i(), props.items.length)) }}
194
+ >
195
+ {item}
196
+ </div>
197
+ )}
198
+ </For>
199
+ </div>
194
200
  </div>
195
201
  )
196
202
  }
@@ -201,19 +201,31 @@ export function Carousel3D(props: Carousel3DProps): JSX.Element {
201
201
 
202
202
  // --- pointer drag: horizontal movement past a threshold advances one slide
203
203
  // per threshold crossed, so a long drag can page through several slides. --
204
- const [dragging, setDragging] = createSignal(false)
204
+ // Capture is DEFERRED until the pointer actually moves past a small
205
+ // threshold. Capturing on pointerdown (the old behaviour) retargeted the
206
+ // pointer to the stage and swallowed the `click` on the chevron buttons and
207
+ // any interactive slide content. A plain click no longer starts a drag, so
208
+ // those clicks work again.
205
209
  let startX = 0
210
+ let pointerId: number | null = null
211
+ let captured = false
212
+ const CAPTURE_THRESHOLD = 8
206
213
  const DRAG_THRESHOLD = 60
207
214
 
208
215
  const onPointerDown = (e: PointerEvent): void => {
209
216
  if (count() < 2) return
210
217
  startX = e.clientX
211
- setDragging(true)
212
- ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
218
+ pointerId = e.pointerId
219
+ captured = false
213
220
  }
214
221
  const onPointerMove = (e: PointerEvent): void => {
215
- if (!dragging()) return
222
+ if (pointerId === null) return
216
223
  const dx = e.clientX - startX
224
+ if (!captured) {
225
+ if (Math.abs(dx) < CAPTURE_THRESHOLD) return
226
+ captured = true
227
+ ;(e.currentTarget as HTMLElement).setPointerCapture(pointerId)
228
+ }
217
229
  if (dx <= -DRAG_THRESHOLD) {
218
230
  next()
219
231
  startX = e.clientX
@@ -222,8 +234,16 @@ export function Carousel3D(props: Carousel3DProps): JSX.Element {
222
234
  startX = e.clientX
223
235
  }
224
236
  }
225
- const endDrag = (): void => {
226
- setDragging(false)
237
+ const endDrag = (e: PointerEvent): void => {
238
+ if (pointerId !== null && captured) {
239
+ try {
240
+ ;(e.currentTarget as HTMLElement).releasePointerCapture(pointerId)
241
+ } catch {
242
+ /* already released */
243
+ }
244
+ }
245
+ pointerId = null
246
+ captured = false
227
247
  }
228
248
 
229
249
  return (
@@ -266,6 +286,7 @@ export function Carousel3D(props: Carousel3DProps): JSX.Element {
266
286
  type="button"
267
287
  aria-label="Previous slide"
268
288
  disabled={count() === 0 || active() === 0}
289
+ onPointerDown={(e) => e.stopPropagation()}
269
290
  onClick={prev}
270
291
  class="absolute left-2 top-1/2 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-primary text-primary-foreground shadow transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40"
271
292
  >
@@ -275,6 +296,7 @@ export function Carousel3D(props: Carousel3DProps): JSX.Element {
275
296
  type="button"
276
297
  aria-label="Next slide"
277
298
  disabled={count() === 0 || active() === count() - 1}
299
+ onPointerDown={(e) => e.stopPropagation()}
278
300
  onClick={next}
279
301
  class="absolute right-2 top-1/2 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-primary text-primary-foreground shadow transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40"
280
302
  >